GitHub Link: https://github.com/jrebull/Vision/blob/main/MNA_Vision_Team13_2_2_Simple_ImgProcessing.ipynb¶

🎓 Maestría en Inteligencia Artificial Aplicada¶

Institution Course Activity


🖼️ Visión Computacional para Imágenes y Video¶

👨‍🏫 Profesores¶

  • Profesor Titular: Dr. Gilberto Ochoa Ruiz
  • Profesor Asistente: MIP Ma. del Refugio Melendez Alfaro
  • Profesor Tutor: M. en C. Jose Angel Martinez Navarro

📊 Actividad: Procesamiento Simple de Imágenes¶

📌 Detalles de la Actividad¶

  • Código: 2.2 Google Colab - Simple_ImgProcessing
  • Título: Técnicas de mejoramiento de imágenes
  • Fecha de entrega: 📅 Septiembre 21, 2025 a las 23:59
  • Puntos: 5 puntos
  • Formato de entrega: PDF y ZIP
  • Disponible: 13 de sep en 0:00 - 21 de sep en 23:59

👥 Team 13¶

🚀 Nuestro Equipo¶

Javier Augusto Rebull Saucedo¶

Javier Augusto Rebull Saucedo

Matrícula: A01795838
🎓 MNA Student

Juan Carlos Pérez Nava¶

Juan Carlos Pérez Nava

Matrícula: A01795941
🎓 MNA Student

Luis Gerardo Sánchez Salazar¶

Luis Gerardo Sánchez Salazar

Matrícula: A01232963
🎓 MNA Student

Oscar Enrique García García¶

Oscar Enrique García García

Matrícula: A01016093
🎓 MNA Student

"Trabajando juntos para explorar las fronteras de la visión computacional" 🚀


🎯 Objetivo del Proyecto¶

En esta sesión profundizaremos en el estudio de las técnicas de mejoramiento de imágenes. Los temas discutidos de forma teórica serán abordados aquí de forma práctica usando Google Colab. Exploraremos transformaciones pixel a pixel fundamentales que son ampliamente utilizadas en aplicaciones de visión computacional y aumentación de datos para modelos de inteligencia artificial.


📋 Ejercicios a Implementar¶

1️⃣ Transformaciones Pixel a Pixel para Data Augmentation¶

Las transformaciones pixel a pixel son sumamente utilizadas para aumentar la cantidad de imágenes para entrenar modelos de inteligencia artificial, sobre todo aquellas de tipo fotométrico.

Requisitos:

  • Investigar 3 tipos de transformaciones fotométricas
  • Aplicarlas sobre imágenes propias
  • Justificar su uso en data augmentation
  • Demostrar con ejemplos prácticos

Transformaciones sugeridas a investigar:

  • 🔆 Ajuste de brillo (Brightness adjustment)
  • 🎨 Modificación de saturación (Saturation modification)
  • 🌈 Cambio de matiz (Hue shifting)
  • 📊 Ecualización de histogramas
  • 🔍 Ajuste de contraste
  • 🌫️ Adición de ruido gaussiano

2️⃣ Aplicación del Negativo de Imagen¶

Requisitos:

  • Investigar una aplicación donde obtener el negativo de imagen tenga un valor específico
  • Integrar el código en una celda de Google Colab
  • Justificar brevemente la investigación
  • Hacer una demo sencilla con imágenes reales

Aplicaciones potenciales:

  • 🏥 Imágenes médicas (rayos X)
  • 🔬 Microscopía
  • 🎨 Efectos artísticos
  • 📸 Fotografía analógica digital

3️⃣ Corrección Gamma¶

Requisitos:

  • Investigar una aplicación donde se puede aplicar la corrección de gamma en una imagen
  • Integrar el código en una celda de Google Colab
  • Justificar brevemente la investigación
  • Hacer una demo sencilla mostrando antes y después

Aplicaciones potenciales:

  • 🖥️ Calibración de monitores
  • 📷 Corrección de exposición fotográfica
  • 🎮 Renderizado de videojuegos
  • 📺 Procesamiento de video broadcast

4️⃣ Sustracción de Imágenes¶

Requisitos:

  • Investigar una aplicación donde se puede usar la sustracción de imágenes
  • Integrar el código en una celda de Google Colab
  • Justificar brevemente la investigación
  • Hacer una demo sencilla con ejemplos prácticos

Aplicaciones potenciales:

  • 🚶 Detección de movimiento
  • 🏗️ Detección de cambios en construcción
  • 🔍 Segmentación de fondo
  • 🛡️ Sistemas de seguridad

🛠️ Bibliotecas y Herramientas Utilizadas¶

Biblioteca Descripción Uso Principal
cv2 OpenCV para Python Procesamiento de imágenes
numpy Computación numérica Operaciones matriciales
matplotlib.pyplot Visualización Mostrar imágenes y gráficos
PIL (Pillow) Python Imaging Library Manipulación de imágenes
pillow_heif Soporte para HEIF/HEIC Lectura de formatos modernos
gdown Google Drive downloader Descargar datasets
google.colab Integración con Colab Acceso a Drive

🔧 Referencia Rápida de Funciones Clave¶

Importación de Bibliotecas¶

import cv2
import gdown
import matplotlib.pyplot as plt
import numpy as np
import os
import pillow_heif
from google.colab import drive
from matplotlib import image as mpimg
from PIL import Image, ImageOps
from PIL.ExifTags import TAGS

Transformaciones Pixel a Pixel Básicas¶

Negativo de Imagen¶

def image_negative(img):
    """
    Calcula el negativo de una imagen
    Formula: negative = 255 - img (para imágenes de 8 bits)
    """
    return 255 - img

Corrección Gamma¶

def gamma_correction(img, gamma=1.0):
    """
    Aplica corrección gamma a una imagen
    Formula: output = input^gamma
    """
    invGamma = 1.0 / gamma
    table = np.array([((i / 255.0) ** invGamma) * 255
                      for i in np.arange(0, 256)]).astype("uint8")
    return cv2.LUT(img, table)

Sustracción de Imágenes¶

def image_subtraction(img1, img2):
    """
    Sustrae dos imágenes pixel a pixel
    Útil para detección de cambios y movimiento
    """
    # Convertir a int16 para evitar overflow
    diff = cv2.subtract(img1, img2)
    # O usando valor absoluto de la diferencia
    abs_diff = cv2.absdiff(img1, img2)
    return diff, abs_diff

Ajuste de Brillo¶

def adjust_brightness(img, value=30):
    """
    Ajusta el brillo de una imagen
    value: cantidad a sumar/restar (-255 a 255)
    """
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(hsv)
    lim = 255 - value
    v[v > lim] = 255
    v[v <= lim] += value
    final_hsv = cv2.merge((h, s, v))
    return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

Ajuste de Contraste¶

def adjust_contrast(img, alpha=1.5):
    """
    Ajusta el contraste de una imagen
    alpha > 1: aumenta contraste
    alpha < 1: disminuye contraste
    """
    adjusted = cv2.convertScaleAbs(img, alpha=alpha, beta=0)
    return adjusted

🔑 Conceptos Clave¶

Concepto Descripción Aplicación
📊 Transformación Pixel a Pixel Operación aplicada independientemente a cada pixel Base del procesamiento de imágenes
🔄 Data Augmentation Técnicas para aumentar el dataset de entrenamiento Mejora en modelos de ML
📈 Función de Transferencia Mapeo de valores de entrada a salida Control de características visuales
🎨 Espacio de Color Sistema de representación del color HSV, RGB, LAB, etc.
📐 Operaciones Aritméticas Suma, resta, multiplicación de imágenes Efectos y análisis

📚 Recursos y Referencias¶

Tutoriales Recomendados¶

  • 🔗 OpenCV Histogram Equalization
  • 🔗 Histogram Equalization Tutorial
  • 🔗 Image Processing in Google Colab

Bibliografía¶

  • 📖 Capítulo 3. Digital Image Processing. R. Gonzalez & R. Woods. 4° Edition. Pearson.
  • 📖 Computer Vision: Algorithms and Applications. Richard Szeliski
  • 📖 Learning OpenCV 3. Adrian Kaehler & Gary Bradski

Documentación Oficial¶

  • 📖 OpenCV Documentation
  • 📖 NumPy Documentation
  • 📖 PIL/Pillow Documentation

¡Exploremos el procesamiento de imágenes pixel a pixel! 🚀¶

Team 13 - Computer Vision 2025¶


Made with Python OpenCV Google Colab


📝 Nota: Este documento fue creado para la Actividad 2.2 del curso de Computer Vision
Maestría en Inteligencia Artificial Aplicada - Tecnológico de Monterrey
Modalidad: Equipo
Medio de realización/entrega: En línea
Última actualización: Septiembre 21, 2025

1. Simple Image Operations¶

Table of Contents¶

  1. Librerias
  2. Cargando Imagenes
  3. Redimensionamiento
  4. Negativo
  5. Transformación logarítmica
  6. Umbralización (thresholding)
  7. Cuantización

Instalación de librerías

In [ ]:
# Instala la biblioteca 'gdown' de forma silenciosa (-q) para descargar archivos desde Google Drive.
!pip install -q gdown
# Instala la biblioteca 'pillow-heif' para añadir compatibilidad con imágenes en formato HEIF/HEIC a la biblioteca Pillow.
!pip install pillow-heif
# Instala la biblioteca 'rawpy' de forma silenciosa (-q) para procesar imágenes en formato RAW.
!pip install rawpy -q
Requirement already satisfied: pillow-heif in /usr/local/lib/python3.12/dist-packages (1.1.0)
Requirement already satisfied: pillow>=11.1.0 in /usr/local/lib/python3.12/dist-packages (from pillow-heif) (11.3.0)

Librerias

  • OpenCV (Open Source Computer Vision Library) con el alias cv2. Es una biblioteca utilizada para una amplia gama de tareas de visión por computadora y procesamiento de imágenes, como detección de objetos, filtros, transformaciones, etc.
  • gdown Herramienta para descargar archivos grandes a partir de una URL o ID de Google Drive.
  • Pyplot es una colección de funciones que permiten crear figuras, gráficos y visualizaciones de datos.
  • NumPy es fundamental para la computación científica en Python, ya que proporciona soporte para arreglos y matrices multidimensionales, que es como se representan las imágenes digitalmente.
  • os permite usar funciones del sistema operativo, como crear carpetas o gestionar rutas de archivos.
  • pillow-heif proporciona soporte de lectura y escritura para el formato de imagen de alta eficiencia HEIF (High Efficiency Image File Format) y el contenedor HEIC, comúnmente utilizado en dispositivos de Apple.
  • El módulo drive de la biblioteca Google Colab se utiliza para permitir el acceso a los archivos y carpetas de Google Drive.
  • El módulo image de la biblioteca Matplotlib se utiliza para cargar, leer y guardar imágenes.
  • El módulo PIL Es la librería principal de Python para procesar imágenes. Permite abrir, editar y guardar formatos comunes como JPEG y PNG.
  • El módulo PIL.ExifTags importa un diccionario que traduce los códigos numéricos de los metadatos de una foto a texto comprensible.
  • inspect es un módulo de Python que permite examinar y obtener información en tiempo de ejecución sobre funciones, clases, módulos y pilas de llamadas, útil para depuración y análisis dinámico del código.
In [ ]:
# ==============================================================================
#  CONFIGURACIÓN INICIAL: IMPORTACIÓN DE BIBLIOTECAS
# ==============================================================================
# Este bloque de código centraliza todas las bibliotecas necesarias para el
# proyecto, asegurando que el entorno esté listo para la ejecución de tareas
# de procesamiento de imágenes, análisis numérico y visualización.
# ------------------------------------------------------------------------------

# ----------------------------------------
# 1. Bibliotecas Estándar de Python
# ----------------------------------------
# Módulos incluidos por defecto con Python, para tareas generales.

import os               # Para interactuar con el sistema operativo (rutas, archivos).
import textwrap         # Para formatear y ajustar bloques de texto de manera limpia.
import inspect          # Para obtener información sobre objetos (módulos, funciones, etc.).
import time             # Para medir tiempos de ejecución y optimizar el código.
import gc               # Para la gestión explícita de la memoria (recolector de basura).

# ----------------------------------------
# 2. Computación Numérica y Visualización de Datos
# ----------------------------------------
# Librerías esenciales para el ecosistema de ciencia de datos.

import numpy as np                    # Para cálculos numéricos y operaciones con arrays (base de todo).
import matplotlib.pyplot as plt       # Para crear visualizaciones y gráficos estáticos.
from matplotlib import image as mpimg # Para cargar, mostrar y manipular imágenes dentro de Matplotlib.

# ----------------------------------------
# 3. Procesamiento y Manipulación de Imágenes 🖼️
# ----------------------------------------
# Herramientas especializadas para trabajar con distintos formatos de imagen.

import cv2                                     # OpenCV: librería avanzada para visión por computadora.
from PIL import Image, ImageOps, ImageEnhance  # Pillow (PIL): para abrir, manipular y guardar imágenes.
from PIL.ExifTags import TAGS                  # Para leer los metadatos EXIF (datos de la cámara) de las imágenes.
import pillow_heif                             # Para añadir soporte de lectura para imágenes en formato HEIF/HEIC.
import rawpy                                   # Para procesar imágenes en formato RAW de cámaras digitales.

# ----------------------------------------
# 4. Utilidades y Entorno de Colab
# ----------------------------------------
# Herramientas para gestionar archivos, descargas y la integración con Google Drive.

import gdown                    # Para descargar archivos grandes directamente desde Google Drive.
from google.colab import drive  # Para montar y acceder a los archivos de Google Drive en Colab.
from tabulate import tabulate   # Para crear tablas de texto con formato y presentar datos de forma legible.

# --- Verificación Final ---
print("✅ Todas las bibliotecas han sido importadas y organizadas correctamente.")
✅ Todas las bibliotecas han sido importadas y organizadas correctamente.
Drive con las imagenes: https://drive.google.com/drive/folders/1t0LK8JeFW5N1HBFlDLnHUvrKNUwymRLM?usp=share_link¶

Cargando imagenes ¶

In [ ]:
# Monta Google Drive
drive.mount('/content/drive')

# Definimos la ruta en Drive
ruta_drive = '/content/drive/MyDrive/MNA_Vision/2.2_Simple_ImgProcessing/Results'
os.makedirs(ruta_drive, exist_ok=True)
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
In [ ]:
# Esta línea registra el formato .HEIC para que Pillow pueda abrirlo
pillow_heif.register_heif_opener()

# --- Descarga de Imágenes ---

# 1. Descarga de Simba.JPG
id_simba = '1knYSeB5O21US0BbraVv_WbaTkFfNHQy3'
output_simba = 'Simba.JPG'
gdown.download(id=id_simba, output=output_simba, quiet=True)

# 2. Descarga de LinearGradient.jpg
id_gradient = '1qUSHu3RVHOI00BWhCI3D26VjYHIKdOJs'
output_gradient = 'LinearGradient.jpg'
gdown.download(id=id_gradient, output=output_gradient, quiet=True)


# 3. Descarga de JARS July 2025.jpg
id_jars = '13Hp5YVZdxYYv4P3SwSRRW7O7p0BPYL7C'
output_jars = 'JARS July 2025.jpg'
gdown.download(id=id_jars, output=output_jars, quiet=True)


# 4. Descarga de NewYork01
id_NY01 = '1zSWyUxCGgspbAjTw1e0XvRt9iBUDCOzJ'
output_NY01 = 'NewYork01.DNG'
gdown.download(id=id_NY01, output=output_NY01, quiet=True)

# 5. Descarga de NYPolice
id_NYPolice = '1hkVGN-iJe3GrFgIX9JRNHvu5-A4mxYKc'
output_NYPolice = 'NYPolice.heic'
gdown.download(id=id_NYPolice , output=output_NYPolice, quiet=True)


print("✅ Descargas completadas")
✅ Descargas completadas
In [ ]:
# Abre la imagen y extrae sus metadatos EXIF.
img_pil = Image.open('Simba.JPG')
exif_data = img_pil._getexif()

# Si existen datos EXIF, los procesa para una tabla:
# convierte IDs a nombres y ajusta el texto si es muy largo.
if exif_data:
  tabla = []
  for tag_id, value in exif_data.items():
      tag = TAGS.get(tag_id, tag_id)

      value_str = str(value)
      if len(value_str) > 50:
        value_str = textwrap.fill(value_str, width=50)

      tabla.append([tag, value_str])

# Imprime los datos EXIF en una tabla con un encabezado y formato elegante.
print("\n" + "="*60)
print("📸 DATOS EXIF DE LA IMAGEN 📸".center(60))
print("="*60 + "\n")
print(tabulate(tabla, headers=["Etiqueta", "Valor"], tablefmt="fancy_grid",colalign=('left', 'left')))
============================================================
                📸 DATOS EXIF DE LA IMAGEN 📸                 
============================================================

╒═════════════════════════╤════════════════════════════════════════════════════╕
│ Etiqueta                │ Valor                                              │
╞═════════════════════════╪════════════════════════════════════════════════════╡
│ ResolutionUnit          │ 2                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExifOffset              │ 234                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ Make                    │ Apple                                              │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ Model                   │ iPhone 15 Pro Max                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ Software                │ Instagram                                          │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ Orientation             │ 1                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ DateTime                │ 2025:03:08 16:27:24                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ YCbCrPositioning        │ 1                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ XResolution             │ 72.0                                               │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ YResolution             │ 72.0                                               │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ HostComputer            │ iPhone 15 Pro Max                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExifVersion             │ b'0232'                                            │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ComponentsConfiguration │ b'\x01\x02\x03\x00'                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ShutterSpeedValue       │ 6.631216006216007                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ DateTimeOriginal        │ 2025:03:08 16:27:24                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ DateTimeDigitized       │ 2025:03:08 16:27:24                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ApertureValue           │ 2.970853609083536                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ BrightnessValue         │ 4.280094646554274                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExposureBiasValue       │ 0.0                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ MeteringMode            │ 5                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ Flash                   │ 16                                                 │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ FocalLength             │ 15.659999847383                                    │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ColorSpace              │ 65535                                              │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExifImageWidth          │ 1536                                               │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ FocalLengthIn35mmFilm   │ 120                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SceneCaptureType        │ 0                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ OffsetTime              │ -05:00                                             │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ OffsetTimeOriginal      │ -05:00                                             │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ OffsetTimeDigitized     │ -05:00                                             │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SubsecTimeOriginal      │ 208                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SubjectLocation         │ (2011, 1521, 2204, 1333)                           │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SubsecTimeDigitized     │ 208                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExifImageHeight         │ 2048                                               │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SensingMethod           │ 2                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExposureTime            │ 0.010101010101010102                               │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ FNumber                 │ 2.8                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ SceneType               │ b'\x01'                                            │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExposureProgram         │ 2                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ISOSpeedRatings         │ 200                                                │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ ExposureMode            │ 0                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ FlashPixVersion         │ b'0100'                                            │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ WhiteBalance            │ 0                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ LensSpecification       │ (2.220000028611935, 15.659999847383,               │
│                         │ 1.7799999713880652, 2.8)                           │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ LensMake                │ Apple                                              │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ LensModel               │ iPhone 15 Pro Max back triple camera 15.66mm f/2.8 │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ CompositeImage          │ 2                                                  │
├─────────────────────────┼────────────────────────────────────────────────────┤
│ MakerNote               │ b'Apple iOS\x00\x00\x01MM\x009\x00\x01\x00\t\x00\x │
│                         │ 00\x00\x01\x00\x00\x00\x0f\x00\x02\x00\x07\x00\x00 │
│                         │ \x02\x00\x00\x00\x02\xc0\x00\x03\x00\x07\x00\x00\x │
│                         │ 00h\x00\x00\x04\xc0\x00\x04\x00\t\x00\x00\x00\x01\ │
│                         │ x00\x00\x00\x01\x00\x05\x00\t\x00\x00\x00\x01\x00\ │
│                         │ x00\x00\xc0\x00\x06\x00\t\x00\x00\x00\x01\x00\x00\ │
│                         │ x00\xd0\x00\x07\x00\t\x00\x00\x00\x01\x00\x00\x00\ │
│                         │ x01\x00\x08\x00\n\x00\x00\x00\x03\x00\x00\x05(\x00 │
│                         │ \x0c\x00\n\x00\x00\x00\x02\x00\x00\x05@\x00\r\x00\ │
│                         │ t\x00\x00\x00\x01\x00\x00\x00\t\x00\x0e\x00\t\x00\ │
│                         │ x00\x00\x01\x00\x00\x00\x00\x00\x10\x00\t\x00\x00\ │
│                         │ x00\x01\x00\x00\x00\x01\x00\x14\x00\t\x00\x00\x00\ │
│                         │ x01\x00\x00\x00\x0c\x00\x17\x00\x10\x00\x00\x00\x0 │
│                         │ 1\x00\x00\x05P\x00\x19\x00\t\x00\x00\x00\x01\x01\x │
│                         │ 00 "\x00\x1a\x00\x02\x00\x00\x00\x06\x00\x00\x05X\ │
│                         │ x00\x1d\x00\n\x00\x00\x00\x01\x00\x00\x05^\x00\x1f │
│                         │ \x00\t\x00\x00\x00\x01\x00\x00\x00\x01\x00 \x00\x0 │
│                         │ 2\x00\x00\x00%\x00\x00\x05f\x00!\x00\n\x00\x00\x00 │
│                         │ \x01\x00\x00\x05\x8b\x00#\x00\t\x00\x00\x00\x02\x0 │
│                         │ 0\x00\x05\x93\x00%\x00\x10\x00\x00\x00\x01\x00\x00 │
│                         │ \x05\x9b\x00&\x00\t\x00\x00\x00\x01\x00\x00\x00\x0 │
│                         │ 3\x00\'\x00\n\x00\x00\x00\x01\x00\x00\x05\xa3\x00+ │
│                         │ \x00\x02\x00\x00\x00%\x00\x00\x05\xab\x00-         │
│                         │ \x00\t\x00\x00\x00\x01\x00\x00\x13\x08\x00.\x00\t\ │
│                         │ x00\x00\x00\x01\x00\x00\x00\x01\x00/\x00\t\x00\x00 │
│                         │ \x00\x01\x00\x00\x00\x82\x000\x00\n\x00\x00\x00\x0 │
│                         │ 1\x00\x00\x05\xd0\x006\x00\t\x00\x00\x00\x01\x00\x │
│                         │ 00\x05o\x007\x00\t\x00\x00\x00\x01\x00\x00\x00\x08 │
│                         │ \x008\x00\t\x00\x00\x00\x01\x00\x00\x01$\x009\x00\ │
│                         │ t\x00\x00\x00\x01\x00\x00\x00\x00\x00:\x00\t\x00\x │
│                         │ 00\x00\x01\x00\x00\x00\x80\x00;\x00\t\x00\x00\x00\ │
│                         │ x01\x00\x00\x00\x00\x00<\x00\t\x00\x00\x00\x01\x00 │
│                         │ \x00\x00\x04\x00=\x00\t\x00\x00\x00\x01\x00\x00\x0 │
│                         │ 0d\x00?\x00\t\x00\x00\x00\x01\x00\x00\x00\x00\x00@ │
│                         │ \x00\x07\x00\x00\x00J\x00\x00\x05\xd8\x00A\x00\t\x │
│                         │ 00\x00\x00\x01\x00\x00\x00\x00\x00B\x00\t\x00\x00\ │
│                         │ x00\x01\x00\x00\x00\x00\x00C\x00\t\x00\x00\x00\x01 │
│                         │ \x00\x00\x00\x00\x00D\x00\t\x00\x00\x00\x01\x00\x0 │
│                         │ 0\x00\x00\x00E\x00\t\x00\x00\x00\x01\x00\x00\x00\x │
│                         │ 00\x00F\x00\t\x00\x00\x00\x01\x00\x00\x00\x00\x00H │
│                         │ \x00\t\x00\x00\x00\x01\x00\x00\x00\x00\x00I\x00\t\ │
│                         │ x00\x00\x00\x01\x00\x00\x00\x00\x00J\x00\t\x00\x00 │
│                         │ \x00\x01\x00\x00\x00\x03\x00M\x00\n\x00\x00\x00\x0 │
│                         │ 1\x00\x00\x06"\x00N\x00\x07\x00\x00\x00o\x00\x00\x │
│                         │ 06*\x00O\x00\x07\x00\x00\x00+\x00\x00\x06\x99\x00P │
│                         │ \x00\x07\x00\x00\x00=\x00\x00\x06\xc4\x00Q\x00\x07 │
│                         │ \x00\x00\x00T\x00\x00\x07\x01\x00R\x00\t\x00\x00\x │
│                         │ 00\x01\xff\xff\xff\xf6\x00S\x00\t\x00\x00\x00\x01\ │
│                         │ x00\x00\x00\x01\x00U\x00\t\x00\x00\x00\x01\x00\x00 │
│                         │ \x00\x00\x00X\x00\t\x00\x00\x00\x01\x00\x00\t\x13\ │
│                         │ x00\x00\x00\x006\x004\x003\x003\x007\x00B\x00W\x00 │
│                         │ \x82\x00\xb9\x00\xe7\x009\x01\xeb\x00\x94\x00\x17\ │
│                         │ x01\x05\x01\xba\x00*\x00,\x00.\x004\x00=\x00U\x00| │
│                         │ \x00\xa4\x00\xdc\x00\x1b\x01\x19\x01\xdb\x00\x86\x │
│                         │ 00\x1e\x01\x0e\x01\xcf\x00*\x00-                   │
│                         │ \x002\x00>\x00]\x00\x7f\x00\x98\x00\xda\x00\xee\x0 │
│                         │ 0\xdd\x00\xe9\x00\xc6\x00x\x00!\x01\x12\x01\xe7\x0 │
│                         │ 0-                                                 │
│                         │ \x004\x00H\x00i\x00\x7f\x00\x9b\x00\xc7\x00\xbf\x0 │
│                         │ 0\xc1\x00\xd5\x00\xe0\x00\xaf\x00k\x00)\x01\x1c\x0 │
│                         │ 1\x01\x01;\x00Y\x00x\x00\x8b\x00\xb5\x00\xc9\x00\x │
│                         │ b7\x00\xc2\x00\xde\x00\xe9\x00\xe0\x00\x7f\x00\\\x │
│                         │ 00.\x01&\x01\x1b\x01r\x00\x8c\x00\xad\x00\xdb\x00\ │
│                         │ xe2\x00\xcd\x00\x8f\x00\xe3\x00\xf6\x00\xe9\x00\xf │
│                         │ 4\x00\xee\x00u\x00\xe4\x00\'\x01)\x01\xa2\x00\xcf\ │
│                         │ x00\xf3\x00\xf4\x00\xe9\x00\xf7\x00\xae\x00\x88\x0 │
│                         │ 0\x87\x00\x8c\x00\x0f\x01*\x01\x1c\x01~\x00\x00\x0 │
│                         │ 16\x01\xec\x00\x08\x01\x06\x01\xfc\x00\t\x01\x0f\x │
│                         │ 01\xe8\x00\xbb\x00\x80\x00\x92\x00D\x00\x86\x00\x9 │
│                         │ 9\x00\xef\x00@\x01F\x01\x13\x01\x10\x01\x05\x01\t\ │
│                         │ x01\n\x01\xff\x00\x0c\x01\xc3\x01\x17\x01e\x01}\x0 │
│                         │ 0\x80\x00\x8a\x00\x11\x01W\x01U\x01\x1b\x01\x19\x0 │
│                         │ 1\x1e\x01\x17\x01\t\x01\xfd\x00\xb9\x00\xd9\x00\xa │
│                         │ 2\x00\x1b\x01\xc5\x00\xf3\x00\xbc\x00\x10\x01n\x01 │
│                         │ j\x01\x0f\x01\x1e\x01\x1f\x01\x11\x01\x08\x01\xb2\ │
│                         │ x00\x8a\x00\xb4\x00&\x01D\x01\xb3\x00\xda\x00\xac\ │
│                         │ x00\x05\x01\x8d\x01\x91\x01\xe9\x00\x06\x01\t\x01\ │
│                         │ x03\x01\xf5\x00\xd5\x00\xd2\x00\xcf\x00\xf6\x00\xb │
│                         │ d\x00\x8e\x00\x99\x00\x9a\x00\t\x01\xa1\x01\x98\x0 │
│                         │ 1\x89\x00\xe5\x00\xfc\x00\xf7\x00\xe0\x00\xd8\x00\ │
│                         │ xdd\x00\xdf\x00\xdb\x00\xcd\x00\xe4\x00m\x01\xf8\x │
│                         │ 00\xe5\x00\x0c\x01\x0c\x01J\x00\x94\x00\xde\x00\xd │
│                         │ e\x00\xdf\x00\xe2\x00\xe4\x00\xe6\x00\xda\x00\xd8\ │
│                         │ x00\xc9\x00G\x01\x83\x01\xe7\x00\xed\x00\xe8\x00/\ │
│                         │ x00N\x00\x90\x00\xcc\x00\xdf\x00\xe6\x00\xe5\x00\x │
│                         │ dc\x00\xdf\x00\xd0\x00\xbe\x00\xb1\x00\xbe\x001\x0 │
│                         │ 1\x10\x01\xd3\x00)\x003\x00S\x00\x9c\x00\xd8\x00\x │
│                         │ e5\x00\xde\x00\xe4\x00\xd4\x00\xc4\x00\xb7\x00\x9a │
│                         │ \x00\x91\x00\xa0\x00\x0c\x01\x18\x01bplist00\xd4\x │
│                         │ 01\x02\x03\x04\x05\x06\x07\x08UflagsUvalueYtimesca │
│                         │ leUepoch\x10\x01\x13\x00\x02\xf9\xb3\x13\xc4\xb11\ │
│                         │ x12;\x9a\xca\x00\x10\x00\x08\x11\x17\x1d\'-        │
│                         │ /8=\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x0 │
│                         │ 0\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x0 │
│                         │ 0\x00\x00\x00\x00\x00\x00?\x00\x00!\xfb\x00\x02\xd │
│                         │ 3\xda\xff\xff\xbd\xa3\x00\x00E\xe3\x00\x00\x1b\xd9 │
│                         │ \x00\x00Z6\x00\x00\x01\xfb\x00\x00\x01\x00\x00\x00 │
│                         │ \x01\x9d\x00\x00\x00\x80\x00\x00\x00\x00\x80P84q82 │
│                         │ 5s\x00\x00\x00\x10I\x00\x04)\xb223090C20-95F8-     │
│                         │ 4030-9ED1-                                         │
│                         │ BA6AA157A3FA\x00\x00\x10(\xaa\x00\x0f\xff\xb5\x00\ │
│                         │ x00\x01\x19\x10\x00\x00\n\x00\x00\x00\x00\x00\x80\ │
│                         │ x10\x8e\x00\x02i\x84\x00\x00\x12\xdf9852F3D0-CEB2- │
│                         │ 495B-8A0C-                                         │
│                         │ 7102881678AC\x00\x00\x02Q_\x00\x00\xe3obplist00\xd │
│                         │ 4\x01\x02\x03\x04\x05\x06\x06\x07Q3Q1Q2Q0\x10\x00" │
│                         │ \x00\x00\x00\x00\x10\x01\x08\x11\x13\x15\x17\x19\x │
│                         │ 1b \x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x0 │
│                         │ 0\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\ │
│                         │ x00\x00\x00\x00\x00\x00\x00"\x00\x03\x90\x0e\x00\x │
│                         │ 00\x19\x95bplist00\xd2\x01\x02\x03\x04Q1Q2\x10\x03 │
│                         │ \xa2\x05\n\xd2\x06\x07\x08\tS2.1S2.2#@\x9c\x84\xdf │
│                         │ @\x00\x00\x00#\x00\x00\x00\x00\x00\x00\x00\x00\xd2 │
│                         │ \x06\x07\t\x0b#@Q\xc0\x00\x00\x00\x00\x00\x08\r\x0 │
│                         │ f\x11\x13\x16\x1b\x1f#,5:\x00\x00\x00\x00\x00\x00\ │
│                         │ x01\x01\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x0 │
│                         │ 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00C │
│                         │ bplist00\x10\x00\x08\x00\x00\x00\x00\x00\x00\x01\x │
│                         │ 01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00 │
│                         │ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\nbpli │
│                         │ st00\xd2\x01\x02\x03\x03T80.2T80.1\x10\x00\x08\r\x │
│                         │ 12\x17\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00 │
│                         │ \x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x │
│                         │ 00\x00\x00\x00\x00\x00\x00\x00\x19bplist00\xd3\x01 │
│                         │ \x02\x03\x04\x05\x06T81.2T81.1T81.3"A\xee\xc9\xef" │
│                         │ B\x05g\xdd"A\xdf\xc3\xd5\x08\x0f\x14\x19\x1e#(\x00 │
│                         │ \x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x │
│                         │ 00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 │
│                         │ \x00\x00\x00\x00\x00-'                             │
╘═════════════════════════╧════════════════════════════════════════════════════╛
In [ ]:
# Cargar y visualizar la imagen.
img1 = mpimg.imread('Simba.JPG')
plt.imshow(img1)
plt.axis('off')
plt.show()

# Mostrar propiedades del arreglo de la imagen.
print(f"El tipo de datos de los píxeles es: \033[36m{img1.dtype}\033[0m")
print(f"Resolución: \033[36m{img1.shape[1]:,}\033[0m x \033[36m{img1.shape[0]:,}\033[0m píxeles | Canales: \033[36m{img1.shape[2]}\033[0m (RGB)")
print(f"Número total de valores en el arreglo: \033[36m{img1.size:,}\033[0m")
No description has been provided for this image
El tipo de datos de los píxeles es: uint8
Resolución: 1,536 x 1,536 píxeles | Canales: 3 (RGB)
Número total de valores en el arreglo: 7,077,888

Una imagen a color se compone de tres capas o canales, donde cada uno representa la intensidad de los píxeles para uno de los colores primarios: rojo, verde y azul (RGB).

A continuación, cargaremos la imagen con OpenCV de dos maneras para observar esta diferencia:

  1. A color (BGR): Usaremos cv2.imread('Simba.JPG'). Por defecto, OpenCV lee la imagen con sus tres canales, pero en el orden BGR (Azul, Verde, Rojo).

  2. En escala de grises: Utilizaremos el modo IMREAD_GRAYSCALE, que reduce la información de los tres canales de color a un solo canal que únicamente representa la intensidad lumínica (blanco y negro).

In [ ]:
# Cargar versiones en color (BGR) y grises de la imagen.
img2 = cv2.imread('Simba.JPG')
img2_gris = cv2.imread('Simba.JPG', cv2.IMREAD_GRAYSCALE)

# Mostrar la imagen a color y sus datos.
plt.imshow(img2)
plt.axis('off')
plt.show()

print(f"El tipo de datos de los píxeles es: \033[36m{img2.dtype}\033[0m")
print(f"Dimensiones de la imagen: \033[36m{img2.shape}\033[0m")
print(f"Resolución: \033[36m{img2.shape[1]:,}\033[0m x \033[36m{img2.shape[0]:,}\033[0m píxeles | Canales: \033[36m{img2.shape[2]}\033[0m (BGR)")
print(f"Número total de valores en el arreglo: \033[36m{img2.size:,}\033[0m")

# Repetir el proceso para la imagen en grises.
plt.imshow(img2_gris, cmap='gray')
plt.axis('off')
plt.show()

print(f"Dimensiones de la imagen en grises: \033[36m{img2_gris.shape}\033[0m")
print(f"Resolución: \033[36m{img2_gris.shape[1]:,}\033[0m x \033[36m{img2_gris.shape[0]:,}\033[0m píxeles")
print(f"Número total de valores en el arreglo: \033[36m{img2_gris.size:,}\033[0m")
No description has been provided for this image
El tipo de datos de los píxeles es: uint8
Dimensiones de la imagen: (1536, 1536, 3)
Resolución: 1,536 x 1,536 píxeles | Canales: 3 (BGR)
Número total de valores en el arreglo: 7,077,888
No description has been provided for this image
Dimensiones de la imagen en grises: (1536, 1536)
Resolución: 1,536 x 1,536 píxeles
Número total de valores en el arreglo: 2,359,296

Para visualizar los canales de color en el orden correcto (Rojo, Verde, Azul), es necesario convertirla al espacio de color RGB con el modo COLOR_BGR2RGB.

In [ ]:
# Corregir el orden de colores a RGB y mostrar.
img2_RGB = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
plt.imshow(img2_RGB)
plt.axis('off')
plt.show()

print(f"El tipo de datos de los píxeles es: \033[36m{img2_RGB.dtype}\033[0m")
print(f"Dimensiones de la imagen: \033[36m{img2_RGB.shape}\033[0m")
print(f"Resolución: \033[36m{img2_RGB.shape[1]:,}\033[0m x \033[36m{img2_RGB.shape[0]:,}\033[0m píxeles | Canales: \033[36m{img2_RGB.shape[2]}\033[0m (RGB)")
print(f"Número total de valores en el arreglo: \033[36m{img2_RGB.size:,}\033[0m")
No description has been provided for this image
El tipo de datos de los píxeles es: uint8
Dimensiones de la imagen: (1536, 1536, 3)
Resolución: 1,536 x 1,536 píxeles | Canales: 3 (RGB)
Número total de valores en el arreglo: 7,077,888

Al usar la librería Pillow, se crea un objeto de tipo Image(Image.open). Este no es directamente una matriz de números, sino un objeto que encapsula la imagen y sus propiedades, ofreciendo métodos específicos para manipularla como .save(), .resize() y .rotate().

In [ ]:
# Cargar y mostrar la imagen con Pillow.
img3 = Image.open('Simba.JPG')
plt.imshow(img3)
plt.axis('off')
plt.show()

# Inspeccionar las propiedades del objeto de imagen.
array = np.array(img3)
total_elementos = array.size

print(f"Objeto: \033[36m{img3}\033[0m")
print(f"Formato: \033[36m{img3.format}\033[0m")
print(f"Tamaño (Ancho x Alto): \033[36m{img3.size}\033[0m")
print(f"Modo de color: \033[36m{img3.mode}\033[0m")
print(f"Número total de valores en el arreglo: \033[36m{total_elementos:,}\033[0m")
No description has been provided for this image
Objeto: <PIL.MpoImagePlugin.MpoImageFile image mode=RGB size=1536x1536 at 0x79486DE42060>
Formato: MPO
Tamaño (Ancho x Alto): (1536, 1536)
Modo de color: RGB
Número total de valores en el arreglo: 7,077,888

La función np.array()se utiliza para transformar un objeto Image de Pillow a su representación como una matriz numérica en NumPy, permitiendo así su manipulación matemática.

In [ ]:
# Convertir a arreglo y examinar sus datos.
img3 = np.array(img3)
alto, ancho, canales = img3.shape

print(f"Dimensiones de la imagen: \033[36m{alto}\033[0m píxeles de alto x \033[36m{ancho}\033[0m píxeles de ancho, con \033[36m{canales}\033[0m canales de color.")

pixel_superior_izquierdo = img3[0, 0]

print(f"Valor del píxel en (0, 0) [R, G, B]: \033[36m{pixel_superior_izquierdo}\033[0m")
Dimensiones de la imagen: 1536 píxeles de alto x 1536 píxeles de ancho, con 3 canales de color.
Valor del píxel en (0, 0) [R, G, B]: [54 44 32]

A continuación, se guarda la matriz de NumPy en un archivo binario .npy

In [ ]:
# Define la ruta completa del archivo a guardar
ruta_completa = os.path.join(ruta_drive, 'img.npy')

# Guarda el array en el archivo .npy dentro de Google Drive
np.save(ruta_completa, img3)

print(f"Archivo guardado con éxito en: \033[34m{ruta_completa}\033[0m ✅")
Archivo guardado con éxito en: /content/drive/MyDrive/MNA_Vision/2.2_Simple_ImgProcessing/Results/img.npy ✅

Redimensionamiento¶

El redimensionamiento es el proceso de cambiar las dimensiones de una imagen digital, es decir, su ancho y alto en píxeles. Se trata de un proceso computacional que implica eliminar o inventar píxeles.

Reducir el tamaño de una imagen es el proceso más sencillo. Para lograrlo, el algoritmo de redimensionamiento analiza grupos de píxeles vecinos en la imagen original y calcula un nuevo píxel único que los represente.

In [ ]:
# Redimensionar la imagen.
orig_img = Image.fromarray(img3)
target_size = (200, 200)
new_img = orig_img.resize(target_size)

# Comparar la imagen original con la nueva.
fig, axes = plt.subplots(1, 2, figsize=(10, 5))

axes[0].imshow(orig_img)
axes[0].set_title('Original')
axes[0].set_xlabel(f"Resolución: {orig_img.width} x {orig_img.height}")
axes[0].set_xticks([]); axes[0].set_yticks([])
for spine in axes[0].spines.values(): spine.set_visible(False)


axes[1].imshow(new_img)
axes[1].set_title('Redimensionada')
axes[1].set_xlabel(f"Resolución: {new_img.width} x {new_img.height}")
axes[1].set_xticks([]); axes[1].set_yticks([])
for spine in axes[1].spines.values(): spine.set_visible(False)

plt.tight_layout()
plt.show()
No description has been provided for this image

Una técnica común en el manejo de imágenes es el proceso de "ajustar y rellenar" (fit and pad):

  1. Ajustar (Fit)

En esta operación, se reduce el tamaño de la imagen original. Se utiliza un método como .thumbnail() para asegurar que la imagen no se deforme, manteniendo siempre su proporción original.

  1. Rellenar (Pad)

Se crea un lienzo (un fondo de un color y tamaño definidos) y se pega la imagen ya ajustada en el centro. Este proceso rellena el espacio sobrante y crea un borde uniforme, logrando que la imagen final tenga las dimensiones exactas deseadas sin haber sido distorsionada.

In [ ]:
# Definir el tamaño máximo para la miniatura y el lienzo final.
target_size = (180, 180)
final_canvas_size = (200, 200)

# Crear una miniatura manteniendo la proporción de la imagen original.
scaled_img = orig_img.copy()
scaled_img.thumbnail(target_size, Image.Resampling.LANCZOS)

# Centrar la miniatura sobre un nuevo fondo de color.
new_img = Image.new("RGB", final_canvas_size, "blue")
paste_x = (final_canvas_size[0] - scaled_img.width) // 2
paste_y = (final_canvas_size[1] - scaled_img.height) // 2
new_img.paste(scaled_img, (paste_x, paste_y))

# Visualizar las tres etapas del proceso.
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

axes[0].imshow(orig_img)
axes[0].set_title(f"Original: {orig_img.width}x{orig_img.height}")
axes[0].axis('off')

axes[1].imshow(scaled_img)
axes[1].set_title(f"Miniatura: {scaled_img.width}x{scaled_img.height}")
axes[1].axis('off')

axes[2].imshow(new_img)
axes[2].set_title(f"Final: {new_img.width}x{new_img.height}")
axes[2].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Negativo¶

El negativo de una imagen es una versión donde todos los colores y brillos están invertidos. Las áreas claras se vuelven oscuras, las áreas oscuras se vuelven claras, y los colores se transforman en sus opuestos

¿Cómo funciona la inversión?

En una imagen a color de 8 bits, cada canal de color (R, G y B) de cada píxel tiene un valor de intensidad entre 0 (negro/sin color) y 255 (blanco/color puro).

La operación 255 - valor_del_píxel invierte esta intensidad:

  • Un color oscuro se vuelve claro (255 - 30 = 225).

  • Un color claro se vuelve oscuro (255 - 240 = 15).

  • El negro (0) se convierte en blanco (255) y viceversa.

El resultado es una nueva imagen que parece un negativo fotográfico del original.

In [ ]:
# Cargar la imagen y convertirla a formato RGB.
orig_img = cv2.imread('Simba.JPG')
orig_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB)

# Invertir los valores de los píxeles para obtener el negativo.
img_neg = 255 - orig_img

# Comparar la imagen original con su negativo.
fig, axes = plt.subplots(1, 2, figsize=(12, 6))

axes[0].imshow(orig_img)
axes[0].set_title('Original')
axes[0].axis('off')

axes[1].imshow(img_neg)
axes[1].set_title('Negativo')
axes[1].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Transformación logarítmica ¶

Es una técnica de procesamiento de imágenes diseñada para realzar los detalles en las áreas oscuras de una imagen.

Su función principal es expandir el rango de los tonos bajos (oscuros) y reducir el rango de los tonos altos (brillantes), haciendo que los detalles en las sombras sean mucho más visibles.

¿Cómo Funciona?

Amplifica las diferencias entre los valores de los píxeles oscuros. Esto permite que detalles sutiles en las sombras se vuelvan perceptibles.

Atenúa las diferencias entre los valores de los píxeles brillantes. Esto evita que las zonas ya iluminadas pierdan detalle o se saturen.

La transformación se define por la siguiente fórmula:

$$ S = c * log (1 + r) $$

Donde,

  • $r$ = El valor del píxel de entrada (original), típicamente en un rango de 0 a 255.
  • $C$ = Una constante de escalado para ajustar el resultado al rango visible.
  • $S$ = El valor del píxel de salida (transformado) en la nueva imagen mejorada.

El resultado de la operación $log(1 + r)$ produce valores en un rango muy pequeño. Para que estos valores formen una imagen visible, necesitan ser escalados de vuelta al rango completo de 0 a 255. La constante $c$ se calcula para lograr esto de manera óptima con la siguiente fórmula:

$$ c = \frac{255}{\log(1 + \text{valor máximo del píxel})} $$

Su objetivo es asegurar que el píxel más brillante de tu imagen original se mapee al valor más brillante posible en la salida (255).

Al hacer esto, garantizas que estás utilizando todo el rango dinámico disponible (de 0 a 255) y obtienes el mejor contraste posible sin perder información en las luces.

In [ ]:
orig_img = cv2.imread('Simba.JPG')
orig_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB)


# Se usa una copia para no alterar la imagen original.
img_float = np.float32(orig_img)

# Calculamos la constante 'c' usando la imagen en formato float.
c = 255 / np.log(1 + np.max(img_float))

# Aplicamos la transformación logarítmica sobre la imagen en formato float.
log_img_float = c * np.log(1 + img_float)
log_img = np.array(log_img_float, dtype=np.uint8)

fig, axes = plt.subplots(1, 2, figsize=(12, 6))

axes[0].imshow(orig_img)
axes[0].set_title('Imagen Original')
axes[0].axis('off')

axes[1].imshow(log_img)
axes[1].set_title('Transformación Logarítmica')
axes[1].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Umbralización (thresholding) ¶

La umbralización (thresholding) es una técnica fundamental para simplificar imágenes. Funciona estableciendo un umbral (un nivel de gris de referencia) y, basándose en él, convierte cada píxel de la imagen a uno de dos valores, generalmente blanco o negro.

Tipos de Operaciones de Umbralización

Cada regla de umbralización decide qué hacer con los píxeles que están por encima o por debajo del valor de umbral definido.

  1. THRESH_BINARY: Si un píxel es más brillante que el umbral, se convierte en blanco. De lo contrario, se vuelve negro.

  2. THRESH_BINARY_INV: Es la operación inversa. Si un píxel es más brillante que el umbral, se vuelve negro; de lo contrario, blanco.

  3. THRESH_TRUNC: Si un píxel es más brillante que el umbral, se "trunca" y toma el valor exacto del umbral. Los píxeles más oscuros no se modifican.

  4. THRESH_TOZERO: Los píxeles más oscuros que el umbral se vuelven negro. Los más brillantes permanecen sin cambios.

  5. THRESH_TOZERO_INV: Es la operación inversa a la anterior. Los píxeles más brillantes que el umbral se vuelven negro, mientras que los más oscuros no se modifican.

In [ ]:
# Cargar la imagen de gradiente y convertirla a formato RGB.
orig_img = cv2.imread('LinearGradient.jpg')
orig_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB)

# Aplicar diferentes tipos de umbralización (thresholding).
ret,thresh1 = cv2.threshold(orig_img,127,255,cv2.THRESH_BINARY)      # Si el píxel > 127, se convierte en 255 (blanco), sino en 0 (negro).
ret,thresh2 = cv2.threshold(orig_img,127,255,cv2.THRESH_BINARY_INV)  # El inverso del anterior.
ret,thresh3 = cv2.threshold(orig_img,127,255,cv2.THRESH_TRUNC)       # Si el píxel > 127, se trunca a 127. Los demás no cambian.
ret,thresh4 = cv2.threshold(orig_img,127,255,cv2.THRESH_TOZERO)      # Si el píxel > 127, no cambia. Los demás se hacen 0.
ret,thresh5 = cv2.threshold(orig_img,127,255,cv2.THRESH_TOZERO_INV)  # El inverso del anterior.

# Preparar los títulos y las imágenes en listas para mostrarlos fácilmente.
titulos = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
imagenes = [orig_img, thresh1, thresh2, thresh3, thresh4, thresh5]
n = np.arange(6)

# Crear una figura y un bucle para mostrar todas las imágenes en una cuadrícula.
fig = plt.figure(figsize=(20, 8))
for i in n:
  plt.subplot(2,3,i+1), plt.imshow(imagenes[i],'gray')
  plt.title(titulos[i])

plt.show()
No description has been provided for this image

Cuantización de Color mediante K-Means ¶

El objetivo de la cuantización es reducir la gran cantidad de colores de una imagen a un número específico y manejable, conocido como K. Para lograrlo, cada color original es reemplazado por el más cercano de una nueva paleta limitada.

El algoritmo de agrupamiento K-Means es la herramienta ideal para esta tarea. Lo que hace es:

  1. Analizar todos los colores de los píxeles de la imagen.
  2. Agruparlos en 'K' clústeres o grupos.
  3. Calcular el color promedio de cada grupo, que se convierte en el color "representativo".

Finalmente, la imagen se reconstruye utilizando únicamente esa nueva y limitada paleta de 'K' colores, lo que resulta en una imagen con una menor profundidad de color pero que conserva la estructura y apariencia general de la original.

🎨 Configurando el Algoritmo K-Means en OpenCV


🛑 1. Condiciones de Parada: ¡Saber Cuándo Detenerse!¶

Para que el algoritmo funcione de manera eficiente, primero establecemos las condiciones de parada. OpenCV detendrá el proceso tan pronto como se cumpla una de estas dos reglas:

  • 🎯 Convergencia por Precisión (TERM_CRITERIA_EPS): El algoritmo se detiene si los centros de los clústeres se estabilizan. Esto sucede cuando el movimiento de los centros entre una iteración y la siguiente es tan pequeño que se considera que la solución ha convergido.

  • 🔄 Límite de Iteraciones (TERM_CRITERIA_MAX_ITER): El algoritmo se detiene al alcanzar un número máximo de ciclos definidos. Esto actúa como una red de seguridad para garantizar que el proceso termine, incluso si no logra una convergencia perfecta.

Ejemplo Práctico:¶

El código criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) se traduce en la siguiente orden:

Ejecuta el algoritmo y detente en cuanto ocurra lo primero: que completes 10 iteraciones* o que los centros de los clústeres se muevan menos de 1.0 unidad.*


⚙️ 2. Parámetros de la Función kmeans¶

La función kmeans necesita los siguientes "ingredientes" para agrupar los datos:

  • Z (Datos)

    • Es la matriz que contiene todos los datos de entrada, la cual debe ser de tipo np.float32. En el caso de una imagen, esta matriz es una larga lista donde cada fila representa un píxel y las columnas representan sus canales de color (R, G, B).
  • K (Número de Clústeres)

    • Define el número de grupos (o colores finales) que deseas obtener en la imagen resultante.
  • criteria (Criterios de Terminación)

    • Las reglas de parada que definimos anteriormente. Es una tupla que especifica el máximo de iteraciones y la precisión requerida.
  • attempts (Número de Intentos)

    • Indica cuántas veces se ejecutará el algoritmo desde cero con diferentes centros iniciales. Al final, OpenCV selecciona el mejor resultado (aquel con la menor "compactación" o error).
  • flags (Método de Inicialización)

    • Especifica cómo se eligen los centros de los clústeres al inicio. Las opciones más comunes son:
      • cv2.KMEANS_RANDOM_CENTERS: Elige los centros iniciales de forma totalmente aleatoria.
      • cv2.KMEANS_PP_CENTERS: Utiliza el algoritmo "k-Means++", un método más inteligente que generalmente conduce a una convergencia más rápida y resultados superiores. ✨
In [ ]:
# Cargar la imagen de entrada (OpenCV la lee en formato BGR).
orig_img = cv2.imread('Simba.JPG')

# --- Función para reducir el número de colores (cuantización) usando K-Means ---
def quantize_image(imagen, K):

   # Remodelar la imagen a una lista de píxeles (N_píxeles x 3) y convertir a float.
   Z = imagen.reshape((-1,3))
   Z = np.float32(Z)

   # Definir los criterios de parada del algoritmo y el número de intentos.
   criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
   attempts = 10

   # Ejecutar K-Means para agrupar los píxeles en 'K' clústeres (colores).
   ret,label,center=cv2.kmeans(Z,K,None,criteria,attempts,cv2.KMEANS_RANDOM_CENTERS)

   # Convertir los centros (la nueva paleta de colores) a formato uint8.
   center = np.uint8(center)
   # Reemplazar cada píxel original con el color del centroide de su clúster.
   res = center[label.flatten()]
   # Remodelar el arreglo de píxeles a las dimensiones de la imagen original.
   res2 = res.reshape((orig_img.shape))
   return res2

# --- Aplicar la función y visualizar los resultados ---

# Definir la lista de valores de 'K' (número de colores) que se probarán.
k_values = [2, 5, 8]
# Aplicar la cuantización para cada valor de K.
quantized_images = [quantize_image(orig_img, k) for k in k_values]

# Preparar las listas de imágenes y títulos para la visualización.
imagenes = [orig_img] + quantized_images
titulos = ['Imagen Original'] + [f'K = {k}' for k in k_values]

# Crear una cuadrícula de 2x2 para mostrar las imágenes.
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
fig.suptitle('Cuantización de Color con K-Means', fontsize=16)

# Iterar sobre cada imagen y su correspondiente eje para mostrarla.
for ax, img_bgr, titulo in zip(axes.flatten(), imagenes, titulos ):
  # Convertir de BGR a RGB para la correcta visualización con Matplotlib.
  img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
  ax.imshow(img_rgb)
  ax.set_title(titulo)
  ax.axis('off') # Ocultar ejes.

# Ocultar los ejes de las subtramas que no se utilicen.
for i in range(len(imagenes), len(axes.flatten())):
  axes.flatten()[i].axis('off')

# Ajustar el diseño para evitar solapamientos y mostrar el gráfico.
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
No description has been provided for this image
Imagen en alta resolución

🎨 Transformaciones Pixel a Pixel para Data Augmentation¶

📋 Objetivo del Proyecto¶

Las transformaciones pixel a pixel son sumamente utilizadas para aumentar la cantidad de imágenes para entrenar modelos de inteligencia artificial, sobre todo aquellas de tipo fotométrico.

🎯 Tarea Principal¶

Investigar 3 tipos de transformaciones y aplicarlas en el proyecto de Google Colab sobre imágenes propias.


🔍 Requisitos¶

  1. Investigar 3 tipos de transformaciones pixel a pixel
  2. Implementar las transformaciones en Google Colab
  3. Aplicar sobre imágenes propias
  4. Enfoque en transformaciones fotométricas para data augmentation

💡 Importancia¶

  • ✨ Aumenta la cantidad de datos de entrenamiento
  • 🚀 Mejora la generalización de modelos de IA
  • 🎯 Especialmente útil para transformaciones fotométricas
  • 📊 Optimiza el rendimiento sin necesidad de más datos originales

Transformacion 01¶

In [ ]:
# Invertir Colores de la imagen
# Esta función realiza una transformación píxel por píxel, ya que cada píxel de la imagen original se transforma de manera individual.
# El proceso de inversión implica cambiar el color de cada píxel a su valor opuesto en el espectro de colores.

# --- Definición de Funciones para el Procesamiento de Imágenes ---

def ver_imagen(img_path):
    """
    Función simple para abrir y cargar un objeto de imagen desde una ruta.
    """
    img = Image.open(img_path)
    return img

def invertir_colores(img_path):
    """
    Invierte los colores de una imagen (crea un negativo).
    Este proceso cambia cada píxel a su valor opuesto en el espectro de color.
    """
    # Asegura que la imagen esté en modo RGB para una inversión de color consistente.
    img = Image.open(img_path).convert('RGB')
    # Utiliza la función 'invert' de Pillow para realizar la operación.
    inverted_img = ImageOps.invert(img)
    return inverted_img

def log_img(img_path):
    """
    Aplica una transformación logarítmica para realzar los detalles en las
    zonas más oscuras de la imagen.
    """
    # Abre la imagen y la convierte a un arreglo NumPy de tipo float para los cálculos.
    img = Image.open(img_path)
    img_float = np.float32(img)
    # Calcula la constante 'c' para escalar el rango dinámico de salida a [0, 255].
    c = 255 / np.log(1 + np.max(img_float))
    # Aplica la fórmula de transformación logarítmica: s = c * log(1 + r).
    log_img_float = c * np.log(1 + img_float)
    # Convierte el resultado de nuevo a un formato de imagen de 8 bits (uint8) para poder visualizarlo.
    log_img = np.array(log_img_float, dtype=np.uint8)
    return log_img

# --- Bloque Principal de Ejecución ---

# Cargar la imagen original y generar sus versiones transformadas.
img_orig = ver_imagen("NewYork01.DNG")
img_tlog = log_img("NewYork01.DNG")
img_inv = invertir_colores("NewYork01.DNG")

# --- Visualización de Resultados ---

# Crear una figura con tres subplots para mostrar cada imagen una al lado de la otra.
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# Mostrar la imagen original en el primer subplot.
axes[0].imshow(img_orig)
axes[0].set_title(f"Imagen original")
axes[0].axis('off') # Ocultar los ejes.

# Mostrar la imagen con transformación logarítmica en el segundo subplot.
axes[1].imshow(img_tlog)
axes[1].set_title(f"Imagen con transformación Logarítmica")
axes[1].axis('off')

# Mostrar la imagen con colores invertidos en el tercer subplot.
axes[2].imshow(img_inv)
axes[2].set_title(f"Imagen con colores invertidos")
axes[2].axis('off')

# Ajustar el diseño para que no haya superposiciones y mostrar la figura.
plt.tight_layout()
plt.show()
No description has been provided for this image

Transformacion 02¶

In [ ]:
# Convertir a escala de grises
# Esta función realiza una transformación píxel por píxel, ya que cada píxel de la imagen original se convierte
# a un valor de intensidad de gris correspondiente. Este proceso implica calcular el valor de gris a partir
# de los componentes de color (rojo, verde y azul) de cada píxel de la imagen, siguiendo una fórmula predefinida
# La formula que usa cmap para cada pixel es: Gris = 0.299 * Red + 0.587 * Green + 0.114 * Blue.

# --- Definición de Funciones ---

def convertir_a_grises(img_path):
    """
    Convierte una imagen a escala de grises.

    Este proceso transforma cada píxel de color a un único valor de
    intensidad (luminancia) usando el modo 'L' de Pillow. La fórmula
    estándar que se utiliza es: Gris = 0.299*R + 0.587*G + 0.114*B.
    """
    img = Image.open(img_path).convert('L')
    return img

def imagen_original(img_path):
    """
    Carga y devuelve el objeto de imagen original desde una ruta.
    """
    img = Image.open(img_path)
    return img

# --- Ejecución del Código Principal ---

# Cargar la imagen original y su versión en escala de grises usando las funciones.
img = imagen_original('JARS July 2025.jpg')
img_gray = convertir_a_grises('JARS July 2025.jpg')

# --- Visualización de Resultados ---

# Crear una figura con dos subplots para comparar las imágenes.
fig, axes = plt.subplots(1, 2, figsize=(12, 6))

# Mostrar la imagen original en el primer subplot.
axes[0].imshow(img)
axes[0].set_title('Imagen Original')
axes[0].axis('off') # Ocultar los ejes.

# Mostrar la imagen en grises en el segundo subplot.
# Se especifica cmap='gray' para asegurar que se muestre en escala de grises.
axes[1].imshow(img_gray, cmap='gray')
axes[1].set_title(f'Imagen en escalas de grises')
axes[1].axis('off')

# Ajustar el diseño para que no se solapen los títulos y mostrar.
plt.tight_layout()
plt.show()
No description has been provided for this image

Transformacion 03¶

In [ ]:
# Aumentar el brillo de la imagen
# Esta función realiza una transformación píxel por píxel porque cada píxel de la imagen se ajusta
# de manera individual al incrementar su valor de brillo en una cantidad fija. Este ajuste se realiza
# sumando el valor especificado a cada componente RGB de cada píxel, asegurando que los valores
# resultantes se mantengan dentro del rango válido (0-255) mediante la función np.clip.

def aumentar_brillo(img_path, brillo=50):
    """
    Aumenta o disminuye el brillo de una imagen sumando un valor a cada píxel.

    Esta es una transformación píxel por píxel donde se suma el valor de 'brillo'
    a cada componente RGB. Se usa np.clip para asegurar que los valores
    resultantes se mantengan dentro del rango válido [0, 255].

    Args:
        img_path (str): La ruta al archivo de imagen.
        brillo (int): La cantidad de brillo a añadir (puede ser negativo para oscurecer).

    Returns:
        Image: Un objeto de imagen de Pillow con el brillo ajustado.
    """
    # Abrir la imagen y convertirla a un array numpy de 16 bits para evitar errores de desbordamiento al sumar.
    img = Image.open(img_path).convert('RGB')
    img_array = np.array(img, dtype=np.int16)

    # Sumar el valor de brillo a cada componente de color de cada píxel.
    img_array += brillo
    # Limitar los valores resultantes al rango válido de 8 bits [0, 255].
    img_array = np.clip(img_array, 0, 255)

    # Convertir el array de nuevo a un objeto de imagen de Pillow (formato uint8).
    img_bright = Image.fromarray(img_array.astype('uint8'))

    return img_bright

# --- Ejecución Principal ---

# Definir la cantidad de brillo que se va a añadir a la imagen.
brillo = 80
# Cargar la imagen original y la versión con el brillo ajustado.
orig_img = Image.open('NYPolice.heic')
img_bright = aumentar_brillo('NYPolice.heic', brillo)

# --- Visualización de Resultados ---

# Crear una figura con dos subplots para comparar las imágenes.
fig, axes = plt.subplots(1, 2, figsize=(12, 6))

# Mostrar la imagen original en el primer subplot.
axes[0].imshow(orig_img)
axes[0].set_title('Imagen Original')
axes[0].axis('off') # Ocultar los ejes.

# Mostrar la imagen con brillo aumentado en el segundo subplot.
axes[1].imshow(img_bright)
axes[1].set_title(f'Imagen con brillo Ajustado ({brillo:+})')
axes[1].axis('off')

# Ajustar el diseño para que no haya superposiciones y mostrar la figura.
plt.tight_layout()
plt.show()
No description has been provided for this image

Aplicaciones¶

🎯 Tarea Principal¶

Investiga una aplicación donde obtener el negativo de imagen tenga un valor específico e integra el código en en una fila de google collab, justificar brevemente tu investigación y haciendo una demo sencilla.


🔍 Requisitos¶

  1. Investigar una aplicación donde obtener el negativo de una imagen tenga un valor específico.
  2. Implementar las transformaciones en Google Colab.
  3. Aplicar el código en una demo sencilla.

Aplicación Práctica: Negativo de Imagen en Diagnóstico Médico¶

Una de las aplicaciones más significativas del negativo de una imagen se encuentra en el campo del diagnóstico médico, particularmente en radiología y tomografía. 🏥

En imágenes como radiografías o tomografías computarizadas (TC), las áreas de alta densidad (como los huesos) aparecen en tonos blancos o grises claros, mientras que los tejidos blandos y el aire se muestran como gris oscuro o negro. Para ciertos análisis, como la detección de tumores o calcificaciones, invertir esta escala de grises puede ser crucial.

Al aplicar el negativo, un médico puede encontrar que una anomalía de baja densidad, que podría ser una sutil mancha clara sobre un fondo oscuro, se destaque mucho más como una mancha negra sobre un fondo claro. Esta inversión de color mejora el contraste y resalta detalles que podrían pasar desapercibidos en la imagen original, facilitando así un diagnóstico más rápido y preciso.

In [ ]:
id_tumor='1CBuk5deX1jFAMdVoIMM1UczmVvDF9uRW'
output_tumor = 'Tumor.png'
gdown.download(id=id_tumor, output=output_tumor, quiet=True)

# --- 2. Cargar la imagen ---
# El "1" al final indica que se carga la imagen a color
original_image = cv2.imread('Tumor.png', 1)

# Verificar si la imagen se cargó correctamente
if original_image is None:
  print(f"Error: No se pudo cargar la imagen")
else:
  # Convertir de BGR (formato de OpenCV) a RGB (para matplotlib)
  original_image_rgb = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)

  # --- 3. Obtener el negativo de la imagen ---
  # Se resta el valor de cada píxel de 255
  # O se usa la función bitwise_not
  # negative_image = 255 - original_image
  negative_image = cv2.bitwise_not(original_image)
  negative_image_rgb = cv2.cvtColor(negative_image, cv2.COLOR_BGR2RGB)

  # --- 4. Mostrar la imagen original y su negativo ---
  fig, axs = plt.subplots(1, 2, figsize=(10, 5))

  # Mostrar la imagen original
  axs[0].imshow(original_image_rgb)
  axs[0].set_title('Imagen Original')
  axs[0].axis('off')

  # Mostrar la imagen negativa
  axs[1].imshow(negative_image_rgb)
  axs[1].set_title('Imagen Invertida')
  axs[1].axis('off')

  plt.tight_layout()
  plt.show()
No description has been provided for this image

Aplicación Práctica: Corrección Gamma en Vigilancia y Seguridad 📹¶

En el ámbito de la vigilancia y seguridad, es común que las grabaciones se realicen en condiciones de poca luz, dejando áreas de interés en sombras profundas. Esto compromete la capacidad de identificar personas, objetos o detalles cruciales.

La corrección gamma es una técnica fundamental para solucionar este problema. Permite iluminar selectivamente las regiones más oscuras de una imagen (aplicando un valor de gamma inferior a 1) sin sobreexponer o "quemar" las zonas que ya están bien iluminadas.

El resultado es que se revela información visual que antes estaba oculta. Por ejemplo, se pueden aclarar los rasgos faciales de un individuo en una zona sombría, un detalle que puede ser invaluable para una investigación o para el monitoreo en tiempo real.

In [ ]:
#id_camera='1eeNz6vK-av-XDr-3Epoq8wFy-Kcy7Was'
id_camera='1AVA5sMfK8yWSYfXrrAT-Ns4J1r2WjnSl'
output_camera = 'sec_camera.jpg'
gdown.download(id=id_camera, output=output_camera, quiet=True)

# --- 2. Cargar la imagen ---
original_image = cv2.imread(output_camera)
# Convertir la imagen a RGB para una visualización correcta en matplotlib
original_image_rgb = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)

# --- 3. Aplicar la corrección de gamma ---
gamma = 1.7

# Crear una tabla de búsqueda (look-up table - LUT) para la corrección
gamma_inv = 1.0 / gamma
table = np.array([((i / 255.0) ** gamma_inv) * 255 for i in np.arange(0, 256)]).astype("uint8")

# Aplicar la tabla a la imagen
gamma_corrected_image = cv2.LUT(original_image, table)
gamma_corrected_image_rgb = cv2.cvtColor(gamma_corrected_image, cv2.COLOR_BGR2RGB)

# --- 4. Mostrar el resultado ---
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
fig.suptitle(f'Corrección de Gamma (valor = {gamma})', fontsize=16)

# Imagen Original
axs[0].imshow(original_image_rgb)
axs[0].set_title('Imagen Original')
axs[0].axis('off')

# Imagen Corregida
axs[1].imshow(gamma_corrected_image_rgb)
axs[1].set_title('Imagen con Corrección de Gamma')
axs[1].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Aplicación Práctica: Sustracción de Imágenes en Control de Calidad 🏭¶

En la industria manufacturera, la sustracción de imágenes es una técnica clave para la inspección automatizada de productos en una línea de montaje. Permite detectar defectos, piezas faltantes o ensamblajes incorrectos de forma rápida y precisa.


El Proceso de Inspección¶

El método se basa en una comparación directa y se desarrolla en tres pasos principales:

  1. Establecer la Referencia: Primero, se captura una imagen de un producto "perfecto" o modelo base. Esta será la imagen de referencia.

  2. Capturar la Muestra: A medida que cada nuevo producto avanza por la línea, una cámara le toma una imagen bajo las mismas condiciones de iluminación y ángulo.

  3. Calcular la Diferencia: El software resta la imagen del nuevo producto de la imagen de referencia.


Análisis del Resultado¶

El resultado de la sustracción revela inmediatamente la calidad del producto:

  • Producto Correcto: Si el nuevo producto es idéntico al de referencia, el resultado de la resta es una imagen completamente negra, indicando que no hay diferencias.
  • Producto Defectuoso: Si existe un defecto (como un rasguño, un tornillo faltante o una etiqueta mal alineada), esa anomalía aparecerá como una mancha o contorno brillante en la imagen resultante.

Este sistema automatizado es considerablemente más eficiente y confiable que la inspección visual humana, reduciendo errores y aumentando la velocidad de producción.

In [ ]:
# --- 1. Descarga de Imágenes desde Google Drive ---
# ID y nombre de archivo para la imagen del producto de referencia (perfecto).
perfect_product_id = '1vFzgVDB4RWWl6Hv2hZOsxO4EpuGICEkG'
output_perf_prod = 'perfect_product.heic'
gdown.download(id=perfect_product_id, output=output_perf_prod, quiet=True)

# ID y nombre de archivo para la imagen del producto defectuoso.
defective_product_id = '1gaQq15y5_evctfURWgkF5-wERVi2i9DW'
output_def_prod = 'defective_product.heic'
gdown.download(id=defective_product_id, output=output_def_prod, quiet=True)

# --- 2. Carga y Preparación de las Imágenes ---
# Cargar las imágenes con Pillow y convertirlas directamente a escala de grises (modo 'L').
perfect_product_pil = Image.open(output_perf_prod).convert('L')
defective_product_pil = Image.open(output_def_prod).convert('L')

# Convertir las imágenes de formato Pillow a arreglos de NumPy para poder procesarlas con OpenCV.
perfect_product = np.array(perfect_product_pil)
defective_product = np.array(defective_product_pil)

# --- 3. Verificación de las Imágenes ---
# Comprobar si las imágenes se cargaron correctamente.
if perfect_product is None or defective_product is None:
    print("Error: No se pudieron cargar las imágenes. Verifique los IDs de los archivos.")
else:
    # Comprobar si las imágenes tienen las mismas dimensiones, un requisito para la sustracción.
    if perfect_product.shape != defective_product.shape:
        print("Error: Las imágenes deben tener el mismo tamaño para la sustracción.")
    else:
        # --- 4. Detección de Defectos por Sustracción ---
        # Calcular la diferencia absoluta píxel a píxel entre la imagen de referencia y la defectuosa.
        difference_image = cv2.absdiff(perfect_product, defective_product)
        # Aplicar un umbral: los píxeles con una diferencia > 10 se convierten en blanco (255), el resto en negro (0).
        # Esto resalta claramente las áreas donde se encuentran los defectos.
        _, thresholded_diff = cv2.threshold(difference_image, 10, 255, cv2.THRESH_BINARY)

        # --- 5. Visualización de los Resultados ---
        # Crear una figura con tres subplots para mostrar las imágenes una al lado de la otra.
        fig, axs = plt.subplots(1, 3, figsize=(18, 6))
        fig.suptitle('Detección de Defectos por Sustracción de Imágenes', fontsize=16)

        # Mostrar la imagen de referencia (producto perfecto).
        axs[0].imshow(perfect_product, cmap='gray')
        axs[0].set_title('Producto de Referencia (Perfecto)')
        axs[0].axis('off')

        # Mostrar la imagen del producto con defectos.
        axs[1].imshow(defective_product, cmap='gray')
        axs[1].set_title('Producto Defectuoso')
        axs[1].axis('off')

        # Mostrar la imagen de la diferencia umbralizada, que revela el defecto.
        axs[2].imshow(thresholded_diff, cmap='gray')
        axs[2].set_title('Diferencia Detectada (Defecto)')
        axs[2].axis('off')

        plt.tight_layout()
        plt.show()
No description has been provided for this image

💡 Caso/Aplicación: Detección térmica de componentes propensos a falla 🌡️⚡¶

In [ ]:
# --- 1. Descarga de Archivos de Imagen desde Google Drive ---
# IDs de los archivos en Google Drive que se van a descargar.
id_panel = '1168_CUvcPa_QmAF8nhMItrefmEHjSkHp'
id_robot = '1jpoUFjBPr-Dtl3_LqgSfPQOXGB_dfkFx'
id_cabinet = '1p9IIX3a9YogncRhlttia3cOEaTZ26Z8y'
id_room1 = '1PMZf6f6kdDCuIp9CHdNhgdV_JYCGPZ5r'
id_room2 = '1h32BAdU89glWr5zoqY87qUgoOE1lVFeF'

# Descargar cada imagen usando su ID y asignarle un nombre de archivo local.
gdown.download(id=id_panel, output='panel.JPG', quiet=True)
gdown.download(id=id_robot, output='robot.JPG', quiet=True)
gdown.download(id=id_cabinet, output='cabinet.JPG', quiet=True)
gdown.download(id=id_room1, output='room1.JPG', quiet=True)
gdown.download(id=id_room2, output='room2.JPG', quiet=True)


# --- 2. Función de Utilidad para Visualizar Imágenes ---
def plot_images_row(*images, titles=None, base_width=4, base_height=4):
    """
    Muestra una o más imágenes en una sola fila horizontal.

    Esta función ajusta automáticamente el tamaño de la figura y, si no se
    proporcionan títulos, intenta usar los nombres de las variables pasadas
    en la llamada a la función como títulos para cada imagen.

    Args:
        *images: Secuencia de imágenes (arreglos de NumPy) para mostrar.
        titles (list, optional): Lista de títulos para cada imagen.
                                 Si es None, se infieren de las variables.
        base_width (int, optional): Ancho base para cada subplot.
        base_height (int, optional): Altura base para cada subplot.
    """
    num_images = len(images)
    if num_images == 0:
        raise ValueError("Debes pasar al menos una imagen.")

    # Lógica para obtener automáticamente los títulos a partir de los nombres de las variables.
    if titles is None:
        # Inspecciona el 'stack frame' anterior para obtener el código que llamó a la función.
        frame = inspect.currentframe().f_back
        code_context = inspect.getframeinfo(frame).code_context
        if code_context:
            # Extrae los nombres de los argumentos de la línea de código.
            call_line = code_context[0]
            args_str = call_line[call_line.find('(')+1:call_line.rfind(')')]
            var_names = [a.strip() for a in args_str.split(',') if a.strip() and '=' not in a]
            titles = var_names[:num_images]
        else:
            # Si no se pueden obtener los nombres, usa títulos genéricos.
            titles = [f"Imagen {i+1}" for i in range(num_images)]

    # Crear una figura con subplots en una sola fila.
    fig, axes = plt.subplots(1, num_images, figsize=(base_width * num_images, base_height))
    if num_images == 1: axes = [axes] # Asegura que 'axes' sea siempre una lista iterable.

    # Iterar sobre las imágenes y los ejes para mostrarlas.
    for ax, img, title in zip(axes, images, titles):
        # Detecta si la imagen es en escala de grises para aplicar el mapa de color correcto.
        ax.imshow(img, cmap='gray' if len(img.shape) == 2 else None)
        ax.set_title(title)
        ax.axis('off')

    plt.tight_layout()
    plt.show()

# --- 3. Carga de las Imágenes en Memoria ---
# Cargar las imágenes descargadas en arreglos de NumPy usando Matplotlib.
Car_Panel = mpimg.imread('panel.JPG')
Robot_Arm = mpimg.imread('robot.JPG')
Control_Cabinet = mpimg.imread('cabinet.JPG')
Empty_Room = mpimg.imread('room1.JPG')
Occupied_Room = mpimg.imread('room2.JPG')

Análisis Termográfico para Mantenimiento Predictivo¶

En la industria, la termografía es una técnica esencial para el mantenimiento predictivo. Permite usar cámaras térmicas para visualizar la distribución de temperatura en equipos, identificando puntos de sobrecalentamiento que son indicadores tempranos de una posible falla.

A continuación, se describe un flujo de trabajo típico para la inspección de un panel eléctrico.


Flujo de Procesamiento de la Imagen¶

  1. Adquisición y Recorte (ROI) Primero, se carga la imagen térmica del equipo. Inmediatamente después, se recorta para aislar la región de interés (ROI), eliminando fondos o áreas irrelevantes y centrando el análisis en los componentes críticos.

  2. Cuantización para Simplificar El paso clave es la cuantización de colores. La imagen se procesa para reducir su paleta a solo dos niveles, lo que crea un alto contraste. Esta simplificación es fundamental para eliminar la ambigüedad y resaltar de forma inequívoca las zonas frías de las calientes.

  3. Visualización y Diagnóstico Finalmente, se comparan las imágenes: la original, que muestra el contexto, y la cuantizada, que actúa como un "mapa de alarmas" visual. Las áreas con temperaturas anómalas se vuelven evidentes de inmediato, permitiendo un diagnóstico rápido.


Aplicaciones Clave¶

  • Mantenimiento Predictivo: Detectar componentes eléctricos o mecánicos sobrecalentados antes de que fallen catastróficamente.
  • Seguridad Industrial: Identificar conexiones defectuosas o sobrecargas que representan un riesgo de incendio.
  • Control de Calidad: Asegurar la uniformidad térmica en procesos de manufactura donde la temperatura es un factor crítico.

Este método transforma datos térmicos complejos en información visual clara, permitiendo tomar decisiones rápidas y precisas para garantizar la seguridad y la eficiencia operativa.

In [ ]:
# --- Detección Térmica de Componentes Propensos a Falla ---

# Imprimir un encabezado en la consola para indicar el inicio del análisis.
print(f"="*50)
print(f"Detección térmica de componentes")
print(f"="*50)

# Cargar la imagen térmica del gabinete de control.
orig_img = mpimg.imread('cabinet.JPG')
plot_images_row(orig_img) # Mostrar la imagen original completa.

# Recortar la imagen para eliminar bordes o áreas no deseadas y enfocarse en los componentes.
cropped_img = orig_img[40:orig_img.shape[0]-40, 20:orig_img.shape[1]-20]
orig_img = cropped_img # Actualizar la variable principal con la imagen ya recortada.

# Aplicar cuantización de color con K=2 para segmentar la imagen en dos regiones principales.
# En una imagen térmica, esto separa eficazmente las zonas "calientes" (potenciales fallas) de las "frías" (normales).
Quantized_Thermal = quantize_image(cropped_img, 2)

# Comparar la imagen térmica recortada con su versión cuantizada para resaltar visualmente los puntos calientes.
plot_images_row(cropped_img, Quantized_Thermal)
==================================================
Detección térmica de componentes
==================================================
No description has been provided for this image
No description has been provided for this image

Ejercicio complementario: Integración de Técnicas para Aplicaciones Reales ⚙️¶

Tras explorar y aplicar transformaciones básicas píxel a píxel 🖼️, se abre la posibilidad de construir flujos de procesamiento más complejos que combinan distintas técnicas para abordar problemas reales.
En esta parte de la actividad proponemos un ejercicio complementario, donde se muestra cómo, partiendo de operaciones simples como el ajuste de contraste, la escala de grises o la sustracción de imágenes, se pueden integrar procesos más avanzados que consideran la relación entre píxeles vecinos.

Esta integración permite no solo preparar y limpiar imágenes, sino también destacar patrones, segmentar regiones de interés y generar resultados útiles para la toma de decisiones en contextos prácticos.

En este apartado se presentan dos casos prácticos que ejemplifican esta evolución:

  1. 🏭 Inspección de paneles automotrices
    La detección temprana de defectos en superficies metálicas ayuda a mantener altos estándares de calidad en la producción industrial, reduciendo costos por retrabajos y desperdicio de material.

  2. 📹 Detección de presencia en cámaras de seguridad
    La comparación inteligente de escenas permite identificar automáticamente cambios en un entorno, mejorando la vigilancia y la activación de alarmas.

Estos ejemplos demuestran cómo las técnicas iniciales estudiadas no son ejercicios aislados, sino los cimientos para sistemas robustos de visión por computadora, capaces de aportar valor en ámbitos industriales y de seguridad. 🚀

Combinación de Técnicas en Procesamiento de Imágenes¶

Un flujo de trabajo eficaz en visión por computadora a menudo combina dos tipos de operaciones complementarias: las que actúan sobre píxeles individuales y las que analizan el contexto de sus vecinos. Esta sinergia permite transformar una imagen cruda en una representación útil para la inspección y el análisis.


1. Operaciones Pixel a Pixel: El Fundamento¶

Estas técnicas procesan cada píxel de forma totalmente independiente, sin considerar la información de los píxeles que lo rodean. Su función principal es el pre-procesamiento y la simplificación de la imagen.

  • Conversión a Escala de Grises: Reduce la imagen a un solo canal de color, simplificando la información de brillo y eliminando la complejidad del color.
  • Umbralización (Thresholding): Segmenta la imagen en dos niveles (generalmente blanco y negro) basándose en un umbral de intensidad, aislando objetos del fondo.

2. Operaciones Basadas en Vecindad: Análisis Contextual¶

Estas técnicas avanzadas determinan el nuevo valor de un píxel basándose en un análisis de su entorno o vecindario. Son cruciales para la extracción de características y patrones locales.

  • CLAHE (Ecualización de Histograma Adaptativa): Mejora el contraste local, revelando detalles en regiones que de otro modo serían demasiado oscuras o brillantes.
  • Filtro Gaussiano: Suaviza la imagen y reduce el ruido promediando los valores de los píxeles cercanos, lo que prepara la imagen para un análisis más preciso.
  • Detector de Bordes Canny: Identifica contornos y bordes nítidos (como grietas o rayaduras) al analizar los gradientes de intensidad en el vecindario de cada píxel.

Flujo de Trabajo Integrado¶

El enfoque estándar consiste en una secuencia lógica: primero, se aplican operaciones pixel a pixel para limpiar, normalizar y simplificar la imagen. Una vez preparada, se utilizan técnicas basadas en vecinos sobre esta base sólida para realizar tareas complejas como la reducción de ruido, la mejora del contraste y, finalmente, la extracción de las características necesarias para la detección de defectos o el reconocimiento de objetos.

Este proceso de dos etapas es la base de aplicaciones industriales avanzadas en segmentación, morfología matemática y visión artificial.

💡 Caso/Aplicación: Análisis de defectos en paneles automotrices 🏭🚗¶

En la industria automotriz, la detección temprana de defectos en paneles de aluminio o acero es fundamental para garantizar la calidad del producto final y evitar costos elevados por retrabajos o desperdicio de material. Durante el proceso de manufactura, estos paneles pueden presentar imperfecciones como rayaduras, abolladuras, grietas o inconsistencias en la superficie.

El siguiente código implementa un flujo de procesamiento de imágenes para identificar y resaltar defectos superficiales en paneles, optimizando el proceso de inspección:

1. Carga y preprocesamiento de la imagen¶

  • Se carga la imagen del panel (panel.JPG) y se convierte de formato BGR a RGB y escala de grises, preparando la imagen para su análisis.

2. Mejora de contraste con CLAHE (Contrast Limited Adaptive Histogram Equalization)¶

  • Se aplica CLAHE para resaltar imperfecciones y detalles sutiles, incluso en condiciones de iluminación no uniforme.
  • Esto permite detectar defectos que serían difíciles de visualizar en la imagen original.

3. Reducción de ruido mediante desenfoque Gaussiano¶

  • El desenfoque suaviza la imagen, reduciendo falsos positivos en la detección de bordes.

4. Detección de bordes con el algoritmo Canny¶

  • Se aplica sobre la imagen procesada para resaltar los contornos de los defectos como rayaduras o grietas.

5. Visualización comparativa¶

  • Se muestran los resultados en diferentes etapas:
    • Imagen original y en escala de grises
    • Imagen mejorada con CLAHE y bordes detectados
    • Imagen suavizada y detección final de bordes más precisos

Este proceso permite automatizar la inspección visual de paneles, mejorando la eficiencia, reduciendo la dependencia de revisiones manuales y garantizando un mayor control de calidad en la producción automotriz.

In [ ]:
# --- 1. Carga y Conversión Inicial de la Imagen ---

# Imprimir un encabezado para la sección del análisis.
print(f"="*50)
print(f"Análisis de defectos en paneles automotrices")
print(f"="*50)

# Cargar la imagen del panel. OpenCV la lee por defecto en formato BGR.
image_bgr = cv2.imread('panel.JPG')
# Convertir la imagen a RGB para una correcta visualización en librerías como Matplotlib.
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
# Convertir a escala de grises, un paso necesario para muchas operaciones de procesamiento de imágenes.
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)


# --- 2. Mejora del Contraste para Resaltar Defectos ---

# Crear un objeto CLAHE (Contrast Limited Adaptive Histogram Equalization).
# Esta técnica mejora el contraste localmente, ideal para revelar defectos sutiles como rayones o abolladuras.
clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8))
# Aplicar el filtro CLAHE a la imagen en escala de grises.
clahe_img = clahe.apply(image_gray)


# --- 3. Pre-procesamiento para Detección de Bordes ---

# Aplicar un desenfoque Gaussiano a la imagen con contraste mejorado.
# Esto ayuda a reducir el ruido y puede prevenir la detección de falsos bordes.
clahe_and_blurred = cv2.GaussianBlur(clahe_img, (5, 5), 0)


# --- 4. Detección de Bordes con el Algoritmo Canny ---

# Detectar bordes en la imagen que solo tiene el contraste mejorado (CLAHE).
edges_clahe = cv2.Canny(clahe_img, 50, 150)
# Detectar bordes en la imagen que fue mejorada y luego suavizada (CLAHE + Blur).
# La comparación de ambos resultados ayuda a determinar el mejor pre-procesamiento.
edges_blurred = cv2.Canny(clahe_and_blurred, 50, 150)


# --- 5. Visualización de los Resultados ---

# Mostrar las imágenes iniciales: BGR (original de OpenCV), RGB y escala de grises.
plot_images_row(image_bgr, image_rgb, image_gray)
# Mostrar el resultado de CLAHE y los bordes detectados a partir de él.
plot_images_row(clahe_img, edges_clahe)
# Mostrar el resultado de CLAHE + Desenfoque y los bordes detectados a partir de él.
plot_images_row(clahe_and_blurred, edges_blurred)
==================================================
Análisis de defectos en paneles automotrices
==================================================
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Detección de Movimiento Mediante Sustracción de Fondo¶

Este método permite identificar la presencia de objetos o personas en sistemas de vigilancia comparando una imagen actual con una imagen de referencia de la escena vacía. El objetivo es aislar automáticamente cualquier cambio significativo, como la entrada de un intruso.


Flujo de Procesamiento¶

El proceso se puede agrupar en tres fases lógicas:

1. Preparación y Normalización¶

El primer paso consiste en preparar las imágenes para una comparación precisa.

  • Escala de Grises: Se elimina la información de color para simplificar el análisis y reducir la carga computacional.
  • Recorte de Interés (ROI): Se aísla el área relevante de la imagen, descartando zonas que no requieren monitoreo.

2. Detección de Cambios¶

Aquí es donde se identifican las diferencias entre la escena de referencia y la actual.

  • Sustracción de Imágenes: Al restar la imagen actual de la de referencia, cualquier elemento nuevo aparece como un área no negra.
  • Mejora de Contraste: Se aplica una transformación logarítmica para realzar diferencias sutiles que podrían pasar desapercibidas.
  • Umbralización (Thresholding): La imagen resultante se convierte en una máscara binaria (blanco y negro), donde el blanco representa las áreas de movimiento detectado.

3. Refinamiento y Aislamiento¶

Finalmente, se limpia la máscara binaria para aislar el objeto de interés.

  • Operaciones Morfológicas: Se utilizan técnicas como la erosión y la dilatación para eliminar el ruido (píxeles blancos aislados) y consolidar la forma del objeto detectado.
  • Segmentación y Composición: Se usa la máscara final para "recortar" el objeto de la imagen original y presentarlo sobre un fondo neutro, facilitando su visualización.

Aplicaciones en Sistemas de Seguridad¶

Esta técnica es la base para funcionalidades críticas en sistemas de vigilancia modernos:

  • Detección automática de intrusos en perímetros residenciales o comerciales.
  • Monitoreo de actividad en áreas restringidas o de acceso controlado.
  • Activación inteligente de alarmas o sistemas de grabación para optimizar el almacenamiento y la respuesta.
In [ ]:
# --- 1. Preparación y Pre-procesamiento de las Imágenes ---

# Imprimir un encabezado para la sección del análisis.
print(f"="*50)
print(f"Detección de presencia por sustracción de fondo")
print(f"="*50)

# Recortar ambas imágenes para enfocarse en la misma región de interés (ROI).
Cropped_Empty = Empty_Room[1800:Empty_Room.shape[0]-700, 700:Empty_Room.shape[1]-150]
Cropped_Dog = Occupied_Room[1800:Empty_Room.shape[0]-700, 700:Empty_Room.shape[1]-150]

# Convertir las imágenes de formato flotante (0-1) a entero de 8 bits (0-255).
Cropped_Empty = (Cropped_Empty * 255).astype('uint8')
Cropped_Dog = (Cropped_Dog * 255).astype('uint8')

# Convertir las imágenes recortadas a escala de grises para simplificar la comparación.
Gray_Empty = cv2.cvtColor(Cropped_Empty, cv2.COLOR_BGR2GRAY)
Gray_Dog = cv2.cvtColor(Cropped_Dog, cv2.COLOR_BGR2GRAY)


# --- 2. Detección de Cambios por Sustracción ---

# Calcular la diferencia absoluta entre la imagen con el perro y la habitación vacía.
# Las áreas con diferencias significativas indican la presencia de un nuevo objeto.
diff = cv2.absdiff(Gray_Dog, Gray_Empty)

# Aplicar una transformación logarítmica para realzar las diferencias sutiles y mejorar la detección.
log_diff = (np.log1p(diff) / np.log1p(np.max(diff)) * 255).astype('uint8')

# Crear una máscara binaria (blanco y negro) a partir de la diferencia.
# Los píxeles blancos representan las áreas donde se detectó movimiento o presencia.
_, thresh = cv2.threshold(log_diff, 135, 255, cv2.THRESH_BINARY)


# --- 3. Limpieza de la Máscara con Operaciones Morfológicas ---

# Crear un elemento estructurante (kernel) elíptico para las operaciones de limpieza.
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6,6))
# Aplicar una operación de APERTURA (opening) para eliminar pequeños puntos de ruido blanco.
cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# Aplicar una operación de CLAUSURA (closing) para rellenar pequeños agujeros negros dentro del objeto detectado.
cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel, iterations=2)


# --- 4. Segmentación del Objeto Detectado ---

# Usar la máscara limpia para "recortar" el objeto (perro) de la imagen original a color.
dog_only = cv2.bitwise_and(Cropped_Dog, Cropped_Dog, mask=cleaned)

# Crear una versión del objeto segmentado sobre un fondo blanco.
white_bg = np.ones_like(Cropped_Dog, dtype=np.uint8) * 255 # Fondo blanco.
mask_inv = cv2.bitwise_not(cleaned) # Máscara invertida (fondo blanco, objeto negro).
dog_on_white = cv2.bitwise_and(Cropped_Dog, Cropped_Dog, mask=cleaned) # Objeto recortado.
background = cv2.bitwise_and(white_bg, white_bg, mask=mask_inv) # Fondo con un agujero con la forma del objeto.
final = cv2.add(dog_on_white, background) # Sumar el objeto y el fondo para la imagen final.


# --- 5. Visualización de los Pasos del Proceso ---

# Mostrar las imágenes originales, recortadas y en escala de grises.
plot_images_row(Empty_Room, Cropped_Empty, Gray_Empty)
plot_images_row(Occupied_Room, Cropped_Dog, Gray_Dog)
# Mostrar la diferencia cruda y la diferencia mejorada con la transformación logarítmica.
plot_images_row(diff, log_diff)
# Mostrar la máscara binaria inicial y la máscara después de la limpieza morfológica.
plot_images_row(thresh, cleaned)
# Mostrar los resultados finales: el objeto segmentado sobre fondo negro y sobre fondo blanco.
plot_images_row(dog_only, final)
==================================================
Detección de presencia por sustracción de fondo
==================================================
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

📊 Discusión de Resultados¶

🔍 Deep Dive¶

Análisis de Técnicas de Procesamiento de Imágenes¶

Este análisis recorre un camino progresivo, comenzando con operaciones pixel a pixel fundamentales y avanzando hacia flujos de trabajo que las integran con métodos basados en vecinos. El objetivo es ilustrar cómo estas técnicas transforman la información visual para resolver problemas prácticos en la industria y la inteligencia artificial.


1. Operaciones Pixel a Pixel: Fundamentos y Aplicaciones¶

Estas técnicas, que procesan cada píxel de forma independiente, son la base para la preparación de datos y la resolución de problemas específicos.

Data Augmentation para Inteligencia Artificial¶

En el entrenamiento de modelos de IA, las transformaciones fotométricas son cruciales para crear un conjunto de datos más robusto y variado.

  • Inversión de Colores (Negativo): Ayuda a que el modelo se vuelva invariante a variaciones extremas de iluminación, reconociendo objetos incluso en condiciones atípicas.
  • Conversión a Escala de Grises: Entrena al modelo para que se centre en formas y texturas en lugar de color, mejorando su rendimiento en escenarios con variaciones cromáticas.
  • Ajuste de Brillo: Simula diferentes condiciones de luz, permitiendo que el modelo generalice y funcione de manera fiable tanto en entornos oscuros como muy iluminados.

Aplicaciones Industriales Directas¶

  • Diagnóstico Médico: La inversión de color en radiografías o tomografías puede mejorar el contraste, ayudando a resaltar anomalías como tumores de baja densidad que serían difíciles de ver en la imagen original.
  • Vigilancia y Seguridad: La corrección gamma es fundamental para aclarar zonas en sombra en grabaciones de seguridad sin sobreexponer las áreas ya iluminadas, revelando detalles cruciales.
  • Control de Calidad: La sustracción de imágenes automatiza la detección de defectos al comparar un producto con una plantilla de referencia. Cualquier diferencia, como una pieza faltante, se resalta instantáneamente.
  • Mantenimiento Predictivo: Mediante la cuantización de imágenes termográficas, se simplifica la detección de puntos de sobrecalentamiento en maquinaria, permitiendo identificar fallas potenciales antes de que ocurran.

2. Flujos Integrados: Combinando Técnicas Pixel a Pixel y de Vecindad¶

Para resolver problemas más complejos, se combinan ambos tipos de técnicas en un flujo de trabajo secuencial, donde las operaciones de píxeles preparan el terreno para un análisis más sofisticado.

Caso 1: Detección de Defectos en Paneles Automotrices¶

El objetivo es encontrar microgrietas o rayaduras en superficies metálicas.

  1. Preparación (Pixel a Pixel): La imagen se convierte a escala de grises para simplificarla.
  2. Análisis (Basado en Vecinos): Se aplica CLAHE para realzar el contraste local de los defectos, seguido de un Filtro Gaussiano y el detector Canny para aislar y resaltar los bordes de las imperfecciones.

Caso 2: Detección de Presencia en Cámaras de Seguridad¶

El sistema debe identificar automáticamente la presencia de un objeto o persona.

  1. Preparación (Pixel a Pixel): Se realiza una sustracción de imágenes entre el fondo vacío y el cuadro actual para aislar el cambio.
  2. Análisis (Basado en Vecinos): Se aplican operaciones morfológicas para limpiar el ruido de la imagen resultante y consolidar la forma del objeto detectado, permitiendo una segmentación precisa.

💭 Reflexión Final¶

Conclusión: De los Fundamentos a las Soluciones Aplicadas¶

Este trabajo ha demostrado que las transformaciones píxel a píxel, a pesar de su simplicidad, son los cimientos sobre los que se construyen flujos de trabajo avanzados en visión por computadora. Lejos de ser meros ejercicios académicos, estas operaciones son herramientas fundamentales con un impacto directo en la calidad, la eficiencia y la inteligencia de los sistemas visuales.

Su poder reside en su capacidad para:

  • Mejorar la Calidad Visual: Ajustar el brillo y el contraste para resaltar detalles que de otro modo permanecerían ocultos.
  • Preparar Datos para Análisis: Simplificar la información, como en la conversión a escala de grises, para hacer el procesamiento posterior más eficiente.
  • Potenciar Modelos de IA: Generar datos de entrenamiento variados mediante data augmentation, haciendo que los modelos sean más robustos y generalizables.

El verdadero potencial se desbloquea al integrar estas técnicas con métodos basados en vecinos. Como se ha visto en los casos prácticos, esta sinergia permite construir soluciones robustas para problemas complejos, desde la detección de defectos en manufactura hasta la identificación de presencia en sistemas de seguridad.


En definitiva, comprender cómo y cuándo aplicar estas transformaciones es una habilidad esencial. Este proyecto no solo ha explorado la teoría, sino que la ha traducido en soluciones funcionales, sentando una base práctica sólida para abordar desafíos de mayor envergadura en el futuro.

📚 Referencias¶

Gonzalez, R. C., & Woods, R. E. (2018). Digital image processing (4.ª ed.). Pearson.

Htoon, K. S. (2020, 18 de agosto). A tutorial to histogram equalization. Medium. https://medium.com/@kyawsawhtoon/a-tutorial-to-histogram-equalization-497600f270e2

IMB Microscopy. (2020, 11 de mayo). Introduction to image processing [Video]. YouTube. https://www.youtube.com/watch?v=5tFz5z88JzY

Khandelwal, N. (2025, 21 de enero). Image processing in Python: Algorithms, tools, and methods you should know. Neptune.ai. https://neptune.ai/blog/image-processing-python

Kumar, A. (2025, 11 de septiembre). What is image processing: Overview, applications, benefits, and more. Simplilearn. https://www.simplilearn.com/image-processing-article

NumPy Developers. (s. f.). NumPy documentation (v2.3). Recuperado el 20 de septiembre de 2025, de https://numpy.org/doc/stable/

Ochoa Ruiz, G. (s. f.). Tema 1.1: Introducción [Diapositivas de PowerPoint]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (s. f.). Tema 1.2: Principios básicos [Diapositivas de PowerPoint]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2023). 1.1 Introducción al Módulo 1: Introducción al procesamiento de imágenes [Video]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2023). Bienvenida al curso [Video]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2023). Módulo 1: Introducción y objetivos [Video]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2023). Módulo 2: Introducción y objetivos [Video]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2025). Ecualización por histogramas [Diapositivas de PowerPoint]. Tecnológico de Monterrey.

Ochoa Ruiz, G. (2025). Operaciones básicas en píxeles para PDI [Diapositivas de PowerPoint]. Tecnológico de Monterrey.

OpenCV. (s. f.). Histogram equalization. En OpenCV documentation. Recuperado el 20 de septiembre de 2025, de https://docs.opencv.org/3.4/d4/d1b/tutorial_histogram_equalization.html

OpenCV. (s. f.). OpenCV documentation. Recuperado el 20 de septiembre de 2025, de https://docs.opencv.org

Pillow Contributors. (s. f.). Pillow (PIL Fork) documentation (v11.3.0). Recuperado el 20 de septiembre de 2025, de https://pillow.readthedocs.io/en/stable/

Shiffman, D. (s. f.). Images and pixels. Processing. Recuperado el 20 de septiembre de 2025, de https://processing.org/tutorials/pixels

TIJ Tech Private Limited. (2020, 15 de diciembre). Introduction to image processing using OpenCV in Google Colab. Medium. https://karmatnspyphuntsho-tijtech.medium.com/introduction-to-image-processing-using-opencv-in-google-colab-a449306cb395

Imagen en alta resolución
In [ ]:
print("🚀 Iniciando descarga de 20 imágenes DNG...")

# --- Descarga de Imágenes ---

# 00. Descarga de Simba_Duchado.DNG
id_00 = '19ZIewOjx2bwlqb8RzQ5mLIfgFjIHir0m'
output_00 = 'Simba_Duchado.DNG'
gdown.download(id=id_00, output=output_00, quiet=True)
print("✅ 00 - Simba_Duchado.DNG descargado")

# 01. Descarga de Simba Ball.DNG
id_01 = '1qnaipDQ7jw-dc6AcUF_v5YWC-SQnpvmV'
output_01 = 'Simba_Ball.DNG'
gdown.download(id=id_01, output=output_01, quiet=True)
print("✅ 01 - Simba Ball.DNG descargado")

# 02. Descarga de iPhone 17 PRO.DNG
id_02 = '1l6kwsg-TLbIDCtUv1IF0U3tmZgk60tce'
output_02 = 'iPhone_17_PRO.DNG'
gdown.download(id=id_02, output=output_02, quiet=True)
print("✅ 02 - iPhone 17 PRO.DNG descargado")

# 03. Descarga de Abington Pre Halloween.DNG
id_03 = '11feFfWmuNWJu4zWblpmN1BzSAlWzvxiV'
output_03 = 'Abington_Pre_Halloween.DNG'
gdown.download(id=id_03, output=output_03, quiet=True)
print("✅ 03 - Abington Pre Halloween.DNG descargado")

# 04. Descarga de Trump 2028.DNG
id_04 = '17DgR3iwPi1obvi0Z83J6Jur0Mpq5XuKX'
output_04 = 'Trump_2028.DNG'
gdown.download(id=id_04, output=output_04, quiet=True)
print("✅ 04 - Trump 2028.DNG descargado")

# 05. Descarga de Boston Hans Zimmer 01.DNG
id_05 = '1Ggi5cpsvFv7gPRVovaUS4IsK91hNL3Jc'
output_05 = 'Boston_Hans_Zimmer_01.DNG'
gdown.download(id=id_05, output=output_05, quiet=True)
print("✅ 05 - Boston Hans Zimmer 01.DNG descargado")

# 06. Descarga de Boston Hans Zimmer 02.DNG
id_06 = '1efi6_dkOE-QA4lyPOjK1p2ZQwGpL5nT2'
output_06 = 'Boston_Hans_Zimmer_02.DNG'
gdown.download(id=id_06, output=output_06, quiet=True)
print("✅ 06 - Boston Hans Zimmer 02.DNG descargado")

# 07. Descarga de Boston Hans Zimmer 03.DNG
id_07 = '1B-m7LNwJmRVSGCqAKLZrJozKbp_kUizl'
output_07 = 'Boston_Hans_Zimmer_03.DNG'
gdown.download(id=id_07, output=output_07, quiet=True)
print("✅ 07 - Boston Hans Zimmer 03.DNG descargado")

# 08. Descarga de Simba Sillon Bright.DNG
id_08 = '10Gi6WVxJcMs4PeNzb1hruU8MgxQMq3oU'
output_08 = 'Simba_Sillon_Bright.DNG'
gdown.download(id=id_08, output=output_08, quiet=True)
print("✅ 08 - Simba Sillon Bright.DNG descargado")

# 09. Descarga de Simba Sillon Bright 02.DNG
id_09 = '1dM5GXXzgziPez_nmdR4-Vcw7Xli4e0B5'
output_09 = 'Simba_Sillon_Bright_02.DNG'
gdown.download(id=id_09, output=output_09, quiet=True)
print("✅ 09 - Simba Sillon Bright 02.DNG descargado")

# 10. Descarga de Simba. Super Bright.DNG
id_10 = '1vpJwcIEHp1DEI5fO-5tG3Kg4zbUZEUw2'
output_10 = 'Simba_Super_Bright.DNG'
gdown.download(id=id_10, output=output_10, quiet=True)
print("✅ 10 - Simba. Super Bright.DNG descargado")

# 11. Descarga de Simba Blurry.DNG
id_11 = '1tLFTFG9MF0Q28YvG0wU_uoE0KgRDSAqy'
output_11 = 'Simba_Blurry.DNG'
gdown.download(id=id_11, output=output_11, quiet=True)
print("✅ 11 - Simba Blurry.DNG descargado")

# 12. Descarga de Simba Blurry 02.DNG
id_12 = '1UA_ddhI0ydHQ6GhcTLZ8Hzz7an3h4DuM'
output_12 = 'Simba_Blurry_02.DNG'
gdown.download(id=id_12, output=output_12, quiet=True)
print("✅ 12 - Simba Blurry 02.DNG descargado")

# 13. Descarga de Simba Blurry 03.DNG
id_13 = '1g-IM3_9_JNz4uY5QLV6rDqvhwDTQkksk'
output_13 = 'Simba_Blurry_03.DNG'
gdown.download(id=id_13, output=output_13, quiet=True)
print("✅ 13 - Simba Blurry 03.DNG descargado")

# 14. Descarga de Simba Dark 01.DNG
id_14 = '1qcnhU_eyiWKMenmi-8ZzXo_f3Y_DFCOf'
output_14 = 'Simba_Dark_01.DNG'
gdown.download(id=id_14, output=output_14, quiet=True)
print("✅ 14 - Simba Dark 01.DNG descargado")

# 15. Descarga de Simba Light OK.DNG
id_15 = '16khYRm6IXVaggf_vSNa9HPVrCHLbiANv'
output_15 = 'Simba_Light_OK.DNG'
gdown.download(id=id_15, output=output_15, quiet=True)
print("✅ 15 - Simba Light OK.DNG descargado")

# 16. Descarga de Simba Semi Light.DNG
id_16 = '1-HYIiWxZqVpvL6JrBe4PFa7gDhK87Zl_'
output_16 = 'Simba_Semi_Light.DNG'
gdown.download(id=id_16, output=output_16, quiet=True)
print("✅ 16 - Simba Semi Light.DNG descargado")

# 17. Descarga de Simba Darkeness Semi.DNG
id_17 = '1QNvpjFGqMpJeBG5HG7m__vu1NzqbNN29'
output_17 = 'Simba_Darkeness_Semi.DNG'
gdown.download(id=id_17, output=output_17, quiet=True)
print("✅ 17 - Simba Darkeness Semi.DNG descargado")

# 18. Descarga de Simba Darkness Semi 02.DNG
id_18 = '1Y1pC53mAaWr_2NNegNs6UA12jFYb9Uga'
output_18 = 'Simba_Darkness_Semi_02.DNG'
gdown.download(id=id_18, output=output_18, quiet=True)
print("✅ 18 - Simba Darkness Semi 02.DNG descargado")

# 19. Descarga de Simba Full Darkness.DNG
id_19 = '13swU-csD30-K-SZ9O8ziPAs5qwKoHlMu'
output_19 = 'Simba_Full_Darkness.DNG'
gdown.download(id=id_19, output=output_19, quiet=True)
print("✅ 19 - Simba Full Darkness.DNG descargado")

print("\n🎉 ¡Todas las descargas completadas exitosamente!")
print(f"✅ Total de imágenes descargadas: 20")
🚀 Iniciando descarga de 20 imágenes DNG...
✅ 00 - Simba_Duchado.DNG descargado
✅ 01 - Simba Ball.DNG descargado
✅ 02 - iPhone 17 PRO.DNG descargado
✅ 03 - Abington Pre Halloween.DNG descargado
✅ 04 - Trump 2028.DNG descargado
✅ 05 - Boston Hans Zimmer 01.DNG descargado
✅ 06 - Boston Hans Zimmer 02.DNG descargado
✅ 07 - Boston Hans Zimmer 03.DNG descargado
✅ 08 - Simba Sillon Bright.DNG descargado
✅ 09 - Simba Sillon Bright 02.DNG descargado
✅ 10 - Simba. Super Bright.DNG descargado
✅ 11 - Simba Blurry.DNG descargado
✅ 12 - Simba Blurry 02.DNG descargado
✅ 13 - Simba Blurry 03.DNG descargado
✅ 14 - Simba Dark 01.DNG descargado
✅ 15 - Simba Light OK.DNG descargado
✅ 16 - Simba Semi Light.DNG descargado
✅ 17 - Simba Darkeness Semi.DNG descargado
✅ 18 - Simba Darkness Semi 02.DNG descargado
✅ 19 - Simba Full Darkness.DNG descargado

🎉 ¡Todas las descargas completadas exitosamente!
✅ Total de imágenes descargadas: 20
In [ ]:
# ========================================
# SELECTOR DE VELOCIDAD
# ========================================
MODO_VELOCIDAD = "CALIDAD"  # Opciones: "ULTRA_RAPIDO", "RAPIDO", "NORMAL", "CALIDAD"

# Configuraciones según modo
MODOS = {
    "ULTRA_RAPIDO": {
        "num_imagenes": 5,
        "half_size": True,
        "max_dim": 400,
        "kmeans_iters": 3,
        "kmeans_size": 50,
        "dpi": 50,
        "transformaciones": ["original", "negativo", "grises", "kmeans", "bordes"]
    },
    "RAPIDO": {
        "num_imagenes": 10,
        "half_size": True,
        "max_dim": 600,
        "kmeans_iters": 5,
        "kmeans_size": 80,
        "dpi": 72,
        "transformaciones": ["original", "negativo", "log", "kmeans", "grises", "gamma", "bordes"]
    },
    "NORMAL": {
        "num_imagenes": 20,
        "half_size": True,
        "max_dim": 800,
        "kmeans_iters": 10,
        "kmeans_size": 100,
        "dpi": 100,
        "transformaciones": ["original", "negativo", "log", "kmeans", "grises", "gamma", "bordes", "sepia"]
    },
    "CALIDAD": {
        "num_imagenes": 20,
        "half_size": False,
        "max_dim": 1200,
        "kmeans_iters": 15,
        "kmeans_size": 150,
        "dpi": 150,
        "transformaciones": ["original", "negativo", "log", "kmeans", "grises", "gamma", "bordes", "sepia"]
    }
}

# Obtener configuración del modo seleccionado
config = MODOS[MODO_VELOCIDAD]

# Lista de archivos
archivos_dng = [
    'Simba_Duchado.DNG', 'Simba_Ball.DNG', 'iPhone_17_PRO.DNG',
    'Abington_Pre_Halloween.DNG', 'Trump_2028.DNG', 'Boston_Hans_Zimmer_01.DNG',
    'Boston_Hans_Zimmer_02.DNG', 'Boston_Hans_Zimmer_03.DNG', 'Simba_Sillon_Bright.DNG',
    'Simba_Sillon_Bright_02.DNG', 'Simba_Super_Bright.DNG', 'Simba_Blurry.DNG',
    'Simba_Blurry_02.DNG', 'Simba_Blurry_03.DNG', 'Simba_Dark_01.DNG',
    'Simba_Light_OK.DNG', 'Simba_Semi_Light.DNG', 'Simba_Darkeness_Semi.DNG',
    'Simba_Darkness_Semi_02.DNG', 'Simba_Full_Darkness.DNG'
]

archivos_a_procesar = archivos_dng[:config["num_imagenes"]]
n_trans = len(config["transformaciones"])

print("=" * 70)
print(f"⚡ MODO: {MODO_VELOCIDAD}")
print("=" * 70)
print(f"📊 Configuración:")
print(f"   • Imágenes: {config['num_imagenes']}")
print(f"   • Transformaciones: {n_trans}")
print(f"   • Resolución media: {'Sí' if config['half_size'] else 'No'}")
print(f"   • Tamaño máximo: {config['max_dim']}px")
print(f"   • Calidad salida: {config['dpi']} DPI")
print("=" * 70)

# ========================================
# FUNCIONES OPTIMIZADAS
# ========================================

def cargar_imagen_optimizada(archivo):
    """Carga imagen con configuración optimizada"""
    with rawpy.imread(archivo) as raw:
        img = raw.postprocess(
            half_size=config["half_size"],
            no_auto_bright=False,
            use_camera_wb=True,
            output_bps=8,
            use_auto_wb=False,  # Más rápido
            output_color=rawpy.ColorSpace.sRGB,
            demosaic_algorithm=rawpy.DemosaicAlgorithm.LINEAR  # Más rápido que AHD
        )

    # Redimensionar si es necesario
    h, w = img.shape[:2]
    if max(h, w) > config["max_dim"]:
        scale = config["max_dim"] / max(h, w)
        new_size = (int(w * scale), int(h * scale))
        img = cv2.resize(img, new_size, interpolation=cv2.INTER_LINEAR)  # LINEAR es más rápido que AREA

    return img

# Diccionario de transformaciones
def get_transformations():
    """Retorna diccionario con todas las transformaciones disponibles"""

    def negativo(img):
        return 255 - img

    def logaritmica(img):
        img_log = np.log1p(img.astype(np.float32))
        return (img_log / img_log.max() * 255).astype(np.uint8)

    def kmeans_rapido(img):
        # Submuestrear para velocidad
        h, w = img.shape[:2]
        size = config["kmeans_size"]
        scale_h, scale_w = h/size, w/size
        img_small = cv2.resize(img, (size, int(h/scale_h)))

        Z = img_small.reshape((-1, 3)).astype(np.float32)
        criteria = (cv2.TERM_CRITERIA_MAX_ITER, config["kmeans_iters"], 1.0)
        _, label, center = cv2.kmeans(Z, 4, None, criteria, 1, cv2.KMEANS_PP_CENTERS)

        res = np.uint8(center)[label.flatten()].reshape(img_small.shape)
        return cv2.resize(res, (w, h), interpolation=cv2.INTER_NEAREST)

    def escala_grises(img):
        return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    def gamma(img):
        return np.power(img/255.0, 1.0/1.7) * 255

    def bordes(img):
        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        return cv2.Canny(gray, 30, 100)

    def sepia(img):
        kernel = np.array([[0.272, 0.534, 0.131],
                          [0.349, 0.686, 0.168],
                          [0.393, 0.769, 0.189]])
        return np.clip(img @ kernel.T, 0, 255)

    return {
        "original": lambda x: x,
        "negativo": negativo,
        "log": logaritmica,
        "kmeans": kmeans_rapido,
        "grises": escala_grises,
        "gamma": gamma,
        "bordes": bordes,
        "sepia": sepia
    }

# ========================================
# PROCESAMIENTO POR LOTES
# ========================================

print("\n🚀 Iniciando procesamiento por lotes...")
tiempo_inicio = time.time()

# Crear figura
fig, axes = plt.subplots(config["num_imagenes"], n_trans,
                         figsize=(2.5*n_trans, 2*config["num_imagenes"]))

if config["num_imagenes"] == 1:
    axes = axes.reshape(1, -1)
elif n_trans == 1:
    axes = axes.reshape(-1, 1)

fig.suptitle(f'Modo {MODO_VELOCIDAD}: {config["num_imagenes"]} imgs × {n_trans} trans',
             fontsize=12)

# Obtener transformaciones
trans_dict = get_transformations()

# Procesar por lotes
for i, archivo in enumerate(archivos_a_procesar):
    print(f"[{i+1}/{config['num_imagenes']}] {archivo[:20]}...", end=" ")
    t_img = time.time()

    try:
        # Cargar imagen
        img = cargar_imagen_optimizada(archivo)

        # Aplicar transformaciones seleccionadas
        for j, trans_name in enumerate(config["transformaciones"]):
            trans_func = trans_dict[trans_name]
            result = trans_func(img)

            # Mostrar resultado
            if trans_name == "grises" or trans_name == "bordes":
                axes[i, j].imshow(result, cmap='gray')
            else:
                axes[i, j].imshow(np.uint8(result))

            axes[i, j].axis('off')

            # Títulos solo en primera fila
            if i == 0:
                axes[i, j].set_title(trans_name.upper(), fontsize=8)

            # Nombres solo en primera columna
            if j == 0:
                axes[i, j].set_ylabel(archivo[:10], fontsize=7, rotation=0, ha='right')

        print(f"✓ {time.time()-t_img:.1f}s")

    except Exception as e:
        print(f"✗ Error: {str(e)[:30]}")
        for j in range(n_trans):
            axes[i, j].axis('off')

    # Liberar memoria cada 5 imágenes
    if i % 5 == 0:
        gc.collect()

# Ajustar y guardar
plt.tight_layout()
output_file = f'transformaciones_{MODO_VELOCIDAD.lower()}.png'
plt.savefig(output_file, dpi=config["dpi"], bbox_inches='tight')

tiempo_total = time.time() - tiempo_inicio

# ========================================
# ESTADÍSTICAS FINALES
# ========================================
print("\n" + "=" * 70)
print(f"✅ COMPLETADO - MODO: {MODO_VELOCIDAD}")
print("=" * 70)
print(f"⏱️  Tiempo total: {tiempo_total:.1f} segundos")
print(f"📊 Promedio: {tiempo_total/config['num_imagenes']:.1f} seg/imagen")
print(f"🖼️  Total procesado: {config['num_imagenes'] * n_trans} visualizaciones")
print(f"💾 Guardado como: {output_file}")
print("\n📈 COMPARACIÓN DE MODOS:")
print("   • ULTRA_RAPIDO: ~10-20 segundos (5 imgs, 5 trans)")
print("   • RAPIDO: ~30-60 segundos (10 imgs, 7 trans)")
print("   • NORMAL: ~90-180 segundos (20 imgs, 8 trans)")
print("   • CALIDAD: ~240-480 segundos (20 imgs, 8 trans, alta res)")
print("\n💡 Para cambiar modo, edita: MODO_VELOCIDAD = 'RAPIDO'")
print("=" * 70)

plt.show()
======================================================================
⚡ MODO: CALIDAD
======================================================================
📊 Configuración:
   • Imágenes: 20
   • Transformaciones: 8
   • Resolución media: No
   • Tamaño máximo: 1200px
   • Calidad salida: 150 DPI
======================================================================

🚀 Iniciando procesamiento por lotes...
[1/20] Simba_Duchado.DNG... ✓ 1.8s
[2/20] Simba_Ball.DNG... ✓ 6.4s
[3/20] iPhone_17_PRO.DNG... ✓ 7.4s
[4/20] Abington_Pre_Hallowe... ✓ 5.4s
[5/20] Trump_2028.DNG... ✓ 2.1s
[6/20] Boston_Hans_Zimmer_0... ✓ 3.2s
[7/20] Boston_Hans_Zimmer_0... ✓ 1.7s
[8/20] Boston_Hans_Zimmer_0... ✓ 1.6s
[9/20] Simba_Sillon_Bright.... ✓ 6.0s
[10/20] Simba_Sillon_Bright_... ✓ 8.7s
[11/20] Simba_Super_Bright.D... ✓ 6.0s
[12/20] Simba_Blurry.DNG... ✓ 8.7s
[13/20] Simba_Blurry_02.DNG... ✓ 13.9s
[14/20] Simba_Blurry_03.DNG... ✓ 6.1s
[15/20] Simba_Dark_01.DNG... ✓ 8.2s
[16/20] Simba_Light_OK.DNG... ✓ 6.1s
[17/20] Simba_Semi_Light.DNG... ✓ 8.0s
[18/20] Simba_Darkeness_Semi... ✓ 8.4s
[19/20] Simba_Darkness_Semi_... ✓ 11.4s
[20/20] Simba_Full_Darkness.... ✓ 5.0s

======================================================================
✅ COMPLETADO - MODO: CALIDAD
======================================================================
⏱️  Tiempo total: 158.5 segundos
📊 Promedio: 7.9 seg/imagen
🖼️  Total procesado: 160 visualizaciones
💾 Guardado como: transformaciones_calidad.png

📈 COMPARACIÓN DE MODOS:
   • ULTRA_RAPIDO: ~10-20 segundos (5 imgs, 5 trans)
   • RAPIDO: ~30-60 segundos (10 imgs, 7 trans)
   • NORMAL: ~90-180 segundos (20 imgs, 8 trans)
   • CALIDAD: ~240-480 segundos (20 imgs, 8 trans, alta res)

💡 Para cambiar modo, edita: MODO_VELOCIDAD = 'RAPIDO'
======================================================================
No description has been provided for this image

🎨 Image Playground: Análisis Píxel a Píxel de Archivos RAW (.DNG)¶

Este ejercicio es un espacio para jugar y experimentar con métodos de procesamiento píxel a píxel aplicados directamente sobre tus archivos RAW (.DNG). El objetivo es comprender cómo cada operación modifica la intensidad, el color y el contraste para diagnosticar la exposición, recuperar detalle o resaltar la estructura antes de una edición profunda.

El script carga las imágenes por lotes, aplica las siguientes transformaciones y genera un mosaico comparativo por cada foto:

  • LOG
  • GAMMA
  • KMEANS
  • GRISES
  • BORDES
  • SEPIA
  • NEGATIVO

🔬 Observaciones por Tipo de Resultado¶

A continuación, se detalla cómo interpretar los resultados según las características de la imagen original.

✅ 1. Exposición Correcta¶

  • Características: Rango tonal equilibrado, negros presentes sin empastarse y altas luces sin quemarse.
  • Ejemplo: Simba_Light_OK
  • Análisis de Transformaciones:
    • GRISES: Confirma un contraste saludable.
    • LOG: Aporta una ayuda mínima o innecesaria.
    • GAMMA: Requiere solo ajustes sutiles.

☀️ 2. Sobreexposición¶

  • Características: Blancos "lavados" y pérdida de textura en las altas luces.
  • Ejemplos: Simba_Super_Bright, Simba_Sillon_Bright/_02
  • Análisis de Transformaciones:
    • LOG: Muy útil para comprimir las altas luces y recuperar detalle.
    • GAMMA: Evitar subir su valor para no quemar más la imagen.
    • GRISES: Evidencia claramente las zonas clippeadas.
    • NEGATIVO: Ayuda a detectar halos en los bordes de las altas luces.

🌙 3. Subexposición¶

  • Características: Sombras empastadas y aparición de ruido al intentar levantar la exposición.
  • Ejemplos: Simba_Dark_01, Simba_Darkeness_Semi/_02, Simba_Full_Darkness
  • Análisis de Transformaciones:
    • LOG ➡️ GAMMA: La combinación de ambas es ideal para levantar sombras con menos artefactos.
    • KMEANS: No aporta información relevante en estas condiciones.
    • BORDES: Falla debido a la escasa señal (información) en las zonas oscuras.

🌗 4. Contraste Muy Alto¶

  • Características: Contraste extremo, generalmente causado por luces puntuales o reflejos intensos.
  • Ejemplos: Boston_Hans_Zimmer_01/02/03, iPhone_17_PRO, Trump_2028
  • Análisis de Transformaciones:
    • LOG: Reduce el crush en los negros y controla los highlights.
    • BORDES y KMEANS: Sirven para una lectura estructural de elementos como tipografías, aristas o módulos.
    • GRISES: Facilita la evaluación del reparto tonal general.

🌫️ 5. Contraste Muy Bajo¶

  • Características: Imagen plana, sin microcontraste, a menudo por luz difusa o desenfoque.
  • Ejemplos: Simba_Blurry/_02/_03 y escenas con sombra suave como Abington_Pre_Halloween.
  • Análisis de Transformaciones:
    • GAMMA: Puede mejorar los tonos medios, pero no soluciona la falta de detalle fundamental.
    • BORDES y KMEANS: Rinden poco en imágenes desenfocadas.
    • SEPIA: Queda relegado a un recurso puramente estético.

💡 Síntesis y Flujo de Trabajo Sugerido¶

En resumen, utiliza cada herramienta con un propósito específico:

Propósito Herramienta Recomendada
Diagnóstico Tonal GRISES
Recuperación de Rango Dinámico LOG y GAMMA
Análisis Estructural BORDES / KMEANS
Inspección / Creatividad SEPIA / NEGATIVO